Logo Search packages:      
Sourcecode: teeworlds version File versions  Download package

e_network_server.c

#include <base/system.h>
#include "e_network.h"
#include "e_network_internal.h"

typedef struct
{
      NETCONNECTION conn;
} NETSLOT;

typedef struct NETBAN
{
      NETBANINFO info;
      
      /* hash list */
      struct NETBAN *hashnext;
      struct NETBAN *hashprev;
      
      /* used or free list */
      struct NETBAN *next;
      struct NETBAN *prev;
} NETBAN;

#define MACRO_LIST_LINK_FIRST(object, first, prev, next) \
      { if(first) first->prev = object; \
      object->prev = (void*)0; \
      object->next = first; \
      first = object; }
      
#define MACRO_LIST_LINK_AFTER(object, after, prev, next) \
      { object->prev = after; \
      object->next = after->next; \
      after->next = object; \
      if(object->next) \
            object->next->prev = object; \
      }

#define MACRO_LIST_UNLINK(object, first, prev, next) \
      { if(object->next) object->next->prev = object->prev; \
      if(object->prev) object->prev->next = object->next; \
      else first = object->next; \
      object->next = 0; object->prev = 0; }
      
#define MACRO_LIST_FIND(start, next, expression) \
      { while(start && !(expression)) start = start->next; }

struct NETSERVER
{
      NETSOCKET socket;
      NETSLOT slots[NET_MAX_CLIENTS];
      int max_clients;

      NETBAN *bans[256];
      NETBAN banpool[NET_SERVER_MAXBANS];
      NETBAN *banpool_firstfree;
      NETBAN *banpool_firstused;

      NETFUNC_NEWCLIENT new_client;
      NETFUNC_NEWCLIENT del_client;
      void *user_ptr;
      
      NETRECVINFO recv;
};

NETSERVER *netserver_open(NETADDR bindaddr, int max_clients, int flags)
{
      int i;
      NETSERVER *server;
      NETSOCKET socket = net_udp_create(bindaddr);
      if(socket == NETSOCKET_INVALID)
            return 0;
      
      server = (NETSERVER *)mem_alloc(sizeof(NETSERVER), 1);
      mem_zero(server, sizeof(NETSERVER));
      server->socket = socket;
      server->max_clients = max_clients;
      if(server->max_clients > NET_MAX_CLIENTS)
            server->max_clients = NET_MAX_CLIENTS;
      if(server->max_clients < 1)
            server->max_clients = 1;
      
      for(i = 0; i < NET_MAX_CLIENTS; i++)
            conn_init(&server->slots[i].conn, server->socket);
      
      /* setup all pointers for bans */
      for(i = 1; i < NET_SERVER_MAXBANS-1; i++)
      {
            server->banpool[i].next = &server->banpool[i+1];
            server->banpool[i].prev = &server->banpool[i-1];
      }
      
      server->banpool[0].next = &server->banpool[1];
      server->banpool[NET_SERVER_MAXBANS-1].prev = &server->banpool[NET_SERVER_MAXBANS-2];
      server->banpool_firstfree = &server->banpool[0];

      return server;
}

int netserver_set_callbacks(NETSERVER *s, NETFUNC_NEWCLIENT new_client, NETFUNC_DELCLIENT del_client, void *user)
{
      s->new_client = new_client;
      s->del_client = del_client;
      s->user_ptr = user;
      return 0;
}

int netserver_max_clients(NETSERVER *s)
{
      return s->max_clients;
}

int netserver_close(NETSERVER *s)
{
      /* TODO: implement me */
      return 0;
}

int netserver_drop(NETSERVER *s, int client_id, const char *reason)
{
      /* TODO: insert lots of checks here */
      NETADDR addr;
      netserver_client_addr(s, client_id, &addr);
      
      dbg_msg("net_server", "client dropped. cid=%d ip=%d.%d.%d.%d reason=\"%s\"",
            client_id,
            addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3],
            reason
            );
      conn_disconnect(&s->slots[client_id].conn, reason);

      if(s->del_client)
            s->del_client(client_id, s->user_ptr);
            
      return 0;
}

int netserver_ban_get(NETSERVER *s, int index, NETBANINFO *info)
{
      NETBAN *ban;
      for(ban = s->banpool_firstused; ban && index; ban = ban->next, index--)
            {}
            
      if(!ban)
            return 0;
      *info = ban->info;
      return 1;
}

int netserver_ban_num(NETSERVER *s)
{
      int count = 0;
      NETBAN *ban;
      for(ban = s->banpool_firstused; ban; ban = ban->next)
            count++;
      return count;
}

static void netserver_ban_remove_by_object(NETSERVER *s, NETBAN *ban)
{
      int iphash = (ban->info.addr.ip[0]+ban->info.addr.ip[1]+ban->info.addr.ip[2]+ban->info.addr.ip[3])&0xff;
      dbg_msg("netserver", "removing ban on %d.%d.%d.%d",
            ban->info.addr.ip[0], ban->info.addr.ip[1], ban->info.addr.ip[2], ban->info.addr.ip[3]);
      MACRO_LIST_UNLINK(ban, s->banpool_firstused, prev, next);
      MACRO_LIST_UNLINK(ban, s->bans[iphash], hashprev, hashnext);
      MACRO_LIST_LINK_FIRST(ban, s->banpool_firstfree, prev, next);
}

int netserver_ban_remove(NETSERVER *s, NETADDR addr)
{
      int iphash = (addr.ip[0]+addr.ip[1]+addr.ip[2]+addr.ip[3])&0xff;
      NETBAN *ban = s->bans[iphash];
      
      MACRO_LIST_FIND(ban, hashnext, net_addr_comp(&ban->info.addr, &addr) == 0);
      
      if(ban)
      {
            netserver_ban_remove_by_object(s, ban);
            return 0;
      }
      
      return -1;
}

int netserver_ban_add(NETSERVER *s, NETADDR addr, int seconds)
{
      int iphash = (addr.ip[0]+addr.ip[1]+addr.ip[2]+addr.ip[3])&0xff;
      unsigned stamp = 0xffffffff;
      NETBAN *ban;
      
      /* remove the port */
      addr.port = 0;
      
      if(seconds)
            stamp = time_timestamp() + seconds;
            
      /* search to see if it already exists */
      ban = s->bans[iphash];
      MACRO_LIST_FIND(ban, hashnext, net_addr_comp(&ban->info.addr, &addr) == 0);
      if(ban)
      {
            /* adjust the ban */
            ban->info.expires = stamp;
            return 0;
      }
      
      if(!s->banpool_firstfree)
            return -1;

      /* fetch and clear the new ban */
      ban = s->banpool_firstfree;
      MACRO_LIST_UNLINK(ban, s->banpool_firstfree, prev, next);
      
      /* setup the ban info */
      ban->info.expires = stamp;
      ban->info.addr = addr;
      
      /* add it to the ban hash */
      MACRO_LIST_LINK_FIRST(ban, s->bans[iphash], hashprev, hashnext);
      
      /* insert it into the used list */
      {
            if(s->banpool_firstused)
            {
                  NETBAN *insert_after = s->banpool_firstused;
                  MACRO_LIST_FIND(insert_after, next, stamp < insert_after->info.expires);
                  
                  if(insert_after)
                        insert_after = insert_after->prev;
                  else
                  {
                        /* add to last */
                        insert_after = s->banpool_firstused;
                        while(insert_after->next)
                              insert_after = insert_after->next;
                  }
                  
                  if(insert_after)
                  {
                        MACRO_LIST_LINK_AFTER(ban, insert_after, prev, next);
                  }
                  else
                  {
                        MACRO_LIST_LINK_FIRST(ban, s->banpool_firstused, prev, next);
                  }
            }
            else
            {
                  MACRO_LIST_LINK_FIRST(ban, s->banpool_firstused, prev, next);
            }
      }

      /* drop banned clients */     
      {
            char buf[128];
            int i;
            NETADDR banaddr;
            
            if(seconds)
                  str_format(buf, sizeof(buf), "you have been banned for %d minutes", seconds/60);
            else
                  str_format(buf, sizeof(buf), "you have been banned for life");
            
            for(i = 0; i < s->max_clients; i++)
            {
                  banaddr = s->slots[i].conn.peeraddr;
                  banaddr.port = 0;
                  
                  if(net_addr_comp(&addr, &banaddr) == 0)
                        netserver_drop(s, i, buf);
            }
      }
      return 0;
}

int netserver_update(NETSERVER *s)
{
      unsigned now = time_timestamp();
      
      int i;
      for(i = 0; i < s->max_clients; i++)
      {
            conn_update(&s->slots[i].conn);
            if(s->slots[i].conn.state == NET_CONNSTATE_ERROR)
                  netserver_drop(s, i, conn_error(&s->slots[i].conn));
      }
      
      /* remove expired bans */
      while(s->banpool_firstused && s->banpool_firstused->info.expires < now)
      {
            NETBAN *ban = s->banpool_firstused;
            netserver_ban_remove_by_object(s, ban);
      }
      
      (void)now;
      
      return 0;
}

/*
      TODO: chopp up this function into smaller working parts
*/
int netserver_recv(NETSERVER *s, NETCHUNK *chunk)
{
      unsigned now = time_timestamp();
      
      while(1)
      {
            NETADDR addr;
            int i, bytes, found;
                  
            /* check for a chunk */
            if(recvinfo_fetch_chunk(&s->recv, chunk))
                  return 1;
            
            /* TODO: empty the recvinfo */
            bytes = net_udp_recv(s->socket, &addr, s->recv.buffer, NET_MAX_PACKETSIZE);

            /* no more packets for now */
            if(bytes <= 0)
                  break;
            
            if(unpack_packet(s->recv.buffer, bytes, &s->recv.data) == 0)
            {
                  NETBAN *ban = 0;
                  NETADDR banaddr = addr;
                  int iphash = (addr.ip[0]+addr.ip[1]+addr.ip[2]+addr.ip[3])&0xff;
                  found = 0;
                  banaddr.port = 0;
                  
                  /* search a ban */
                  for(ban = s->bans[iphash]; ban; ban = ban->hashnext)
                  {
                        if(net_addr_comp(&ban->info.addr, &banaddr) == 0)
                              break;
                  }
                  
                  /* check if we just should drop the packet */
                  if(ban)
                  {
                        // banned, reply with a message
                        char banstr[128];
                        if(ban->info.expires)
                        {
                              int mins = ((ban->info.expires - now)+59)/60;
                              if(mins == 1)
                                    str_format(banstr, sizeof(banstr), "banned for %d minute", mins);
                              else
                                    str_format(banstr, sizeof(banstr), "banned for %d minutes", mins);
                        }
                        else
                              str_format(banstr, sizeof(banstr), "banned for life");
                        send_controlmsg(s->socket, &addr, 0, NET_CTRLMSG_CLOSE, banstr, str_length(banstr)+1);
                        continue;
                  }
                  
                  if(s->recv.data.flags&NET_PACKETFLAG_CONNLESS)
                  {
                        chunk->flags = NETSENDFLAG_CONNLESS;
                        chunk->client_id = -1;
                        chunk->address = addr;
                        chunk->data_size = s->recv.data.data_size;
                        chunk->data = s->recv.data.chunk_data;
                        return 1;
                  }
                  else
                  {                 
                        /* TODO: check size here */
                        if(s->recv.data.flags&NET_PACKETFLAG_CONTROL && s->recv.data.chunk_data[0] == NET_CTRLMSG_CONNECT)
                        {
                              found = 0;
                        
                              /* check if we already got this client */
                              for(i = 0; i < s->max_clients; i++)
                              {
                                    if(s->slots[i].conn.state != NET_CONNSTATE_OFFLINE &&
                                          net_addr_comp(&s->slots[i].conn.peeraddr, &addr) == 0)
                                    {
                                          found = 1; /* silent ignore.. we got this client already */
                                          break;
                                    }
                              }
                              
                              /* client that wants to connect */
                              if(!found)
                              {
                                    for(i = 0; i < s->max_clients; i++)
                                    {
                                          if(s->slots[i].conn.state == NET_CONNSTATE_OFFLINE)
                                          {
                                                found = 1;
                                                conn_feed(&s->slots[i].conn, &s->recv.data, &addr);
                                                if(s->new_client)
                                                      s->new_client(i, s->user_ptr);
                                                break;
                                          }
                                    }
                                    
                                    if(!found)
                                    {
                                          const char fullmsg[] = "server is full";
                                          send_controlmsg(s->socket, &addr, 0, NET_CTRLMSG_CLOSE, fullmsg, sizeof(fullmsg));
                                    }
                              }
                        }
                        else
                        {
                              /* normal packet, find matching slot */
                              for(i = 0; i < s->max_clients; i++)
                              {
                                    if(net_addr_comp(&s->slots[i].conn.peeraddr, &addr) == 0)
                                    {
                                          if(conn_feed(&s->slots[i].conn, &s->recv.data, &addr))
                                          {
                                                if(s->recv.data.data_size)
                                                      recvinfo_start(&s->recv, &addr, &s->slots[i].conn, i);
                                          }
                                    }
                              }
                        }
                  }
            }
      }
      return 0;
}

int netserver_send(NETSERVER *s, NETCHUNK *chunk)
{
      if(chunk->data_size >= NET_MAX_PAYLOAD)
      {
            dbg_msg("netserver", "packet payload too big. %d. dropping packet", chunk->data_size);
            return -1;
      }
      
      if(chunk->flags&NETSENDFLAG_CONNLESS)
      {
            /* send connectionless packet */
            send_packet_connless(s->socket, &chunk->address, chunk->data, chunk->data_size);
      }
      else
      {
            int f = 0;
            dbg_assert(chunk->client_id >= 0, "errornous client id");
            dbg_assert(chunk->client_id < s->max_clients, "errornous client id");
            
            if(chunk->flags&NETSENDFLAG_VITAL)
                  f = NET_CHUNKFLAG_VITAL;
            
            conn_queue_chunk(&s->slots[chunk->client_id].conn, f, chunk->data_size, chunk->data);

            if(chunk->flags&NETSENDFLAG_FLUSH)
                  conn_flush(&s->slots[chunk->client_id].conn);
      }
      return 0;
}

void netserver_stats(NETSERVER *s, NETSTATS *stats)
{
      int num_stats = sizeof(NETSTATS)/sizeof(int);
      int *istats = (int *)stats;
      int c, i;

      mem_zero(stats, sizeof(NETSTATS));
      
      for(c = 0; c < s->max_clients; c++)
      {
            if(s->slots[c].conn.state != NET_CONNSTATE_OFFLINE)
            {
                  int *sstats = (int *)(&(s->slots[c].conn.stats));
                  for(i = 0; i < num_stats; i++)
                        istats[i] += sstats[i];
            }
      }
}

NETSOCKET netserver_socket(NETSERVER *s)
{
      return s->socket;
}


int netserver_client_addr(NETSERVER *s, int client_id, NETADDR *addr)
{
      *addr = s->slots[client_id].conn.peeraddr;
      return 1;
}

Generated by  Doxygen 1.6.0   Back to index