/* Verteilte Systeme - Programmieraufgabe Uebungsblatt 8
   Author: Stefan Schonger
   Date  : 06/15/99

   A Demo of how the Routing Information Protocol (RIP) works
   The package format is that of RIP v1 (RFC 1058) and
   we use multicast addresses as in RIP v2 (RFC 2453), but for here
   we treat a multicast group as a simulation of a closed network (with
   the attached computers being the members of the group and a multicast
   packet being a "broadcast" on that simulated network).
   To make it possible to simulate this all on one host, we add a simulated
   "source ip" to the packet and also the destination ip (multicast group)
   because both solaris and linux do not support the option to get it.

*/

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/uio.h>
#include <stdio.h>
#include <stdlib.h> 
#include <string.h>    
#include <unistd.h>  
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>

#define MAX_RIP_ENTRIES 25
#define MAX_TABLE 1000
#define MAX_ATTACHED 20

#define MAX_BUF 66000
#define BUFLEN 128

#define RIP_TIMEOUT 5
/* in real life: ?? */

#define RIP_BROADCAST_TIME 2
/* in real life: 30 */

#define SLEEP_INTERVALL 2


#define DEFAULT_MULTICAST_PORT 4444 
/* the multicast port we use to simulate our networks */

#define RIP_HEADER_SIZE 16


/* RIP address structure */

typedef struct {
  unsigned int netFamily;            /* 16bit Adress family */
  unsigned int pad;                  /* 16bit must be zero */
  struct in_addr ip_address;         /* 32bit IP Address of the Net */
  unsigned long pad1;                /* 32bit must be zero */
  unsigned long pad2;                /* 32bit must be zero */
  unsigned long hopDistance;         /* 32bit hop distance */
} RipEntry;

typedef struct {
  unsigned char  cmd;                /*  8bit request/response */
  unsigned char  vers;               /*  8bit protocol version # */
  unsigned char  rest[2];            /* 16bit pad to 32-bit boundary 
				              (must be zero) */
  struct in_addr from;               /* 32 bit, to simulate several 
					"routers" on one host */
                                     /* NOT in original RIP */
  struct in_addr to;                 /* 32bit not in the procotol, but no
					chance to get the destination
					IP of a UDP packet from solaris 
					( or linux for that matter) */
				     /* NOT in original RIP */
  
  RipEntry ripEntries[ MAX_RIP_ENTRIES ];
  
} Rip;

/*
 * Packet types.
 */
#define RIPCMD_REQUEST          1       /* want info */
#define RIPCMD_RESPONSE         2       /* responding to request */
#define RIPCMD_TRACEON          3       /* (obsolete) turn tracing on */
#define RIPCMD_TRACEOFF         4       /* (obsolete) turn it off */
#define RIPCMD_SUNINTERNAL      5       /* reserved for SUN internal use */

#define RIPCMD_MAX              5

#define HOPCNT_INFINITY         16      /* max 16 hops */



#define IP_RECVDSTADDR 7





/* Routing Table */

typedef struct {
  struct in_addr destination;        /* IP-Adress of the destination 
					  Network (or host) */
  unsigned long hopDistance;            /* Distance to the network */
  struct in_addr nextHop;              /* next Router on the way */
  
  int lastUpdate;

  /* several timers (just one for now) */
  
} RoutingTableEntry;

RoutingTableEntry routingTable[MAX_TABLE];
int routingTableSize;


/* Attached Networks */

typedef struct {
  struct in_addr addr;             /* The address of the subnet */
  int sockfd;                      /* the socket for the multicast group 
				      on addr, port DEFAULT */
} AttachedNetwork;

AttachedNetwork attachedNetworks[MAX_ATTACHED];
int attachedNetworkSize;

struct in_addr localAddr;


void hexDump( char *toDump, size_t size ) {
  size_t i;

  for(i=0; i<size; i++) {
    if(i%10 == 0) puts("");
    printf(" %-2x", (char) toDump[i]);
  }
  puts("");
}

/* return socket fd attached to multicast group addr,port */
int attach(unsigned long addr, int port) {
  int sockfd;
  struct sockaddr_in groupAddr;
  /*  size_t groupAddrLen;*/
  struct ip_mreq multicastRequest;
  /*  char mesgBuf[MAX_BUF];
      int mesgSize;*/
  int sockopt;

  /* Open a UDP socket */
  if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0 )) < 0){
    perror("Can't open socket");
    return -1;
  }
  
    /* allow multiple receivers on one host (using multicast,
       all receivers on one port get the UDP message) */
  sockopt = 1;
  if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *) &sockopt, sizeof(sockopt)) < 0) {
    perror("setsockopt");
    return -1;
  }
  
  /* set up destination address */
  memset((char *) &groupAddr, 0, sizeof(struct sockaddr_in));
  groupAddr.sin_family      = AF_INET;
  groupAddr.sin_addr.s_addr = INADDR_ANY;
  groupAddr.sin_port        = port;
    
  /* bind to receive address */
  if (bind(sockfd,(struct sockaddr *) &groupAddr, sizeof(groupAddr)) < 0) {
    perror("bind");
    return -1;
  }
    
  /* use setsockopt() to request that the kernel join a multicast group */
  multicastRequest.imr_multiaddr.s_addr = addr;
  multicastRequest.imr_interface.s_addr = htonl(INADDR_ANY);
  if (setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,
		 (void *) &multicastRequest, sizeof(multicastRequest)) < 0) {
    perror("setsockopt");
    return -1;
  }

  /* set non-blocking (but did not seem to work => select() used) */
  if( fcntl(sockfd, F_SETFD, O_NONBLOCK) < 0) {
    perror("fcntl error (O_NONBLOCK)");
  }
					      
  return sockfd;
  
}

/* check if a routing table entry is expired (and delete them) */
void checkTimeouts(int time) {
  int i,j;

  for(i=0; i<routingTableSize; i++) {
    if( time - routingTable[i].lastUpdate > RIP_TIMEOUT ) {
      printf("!! TIMEOUT !! deleting route to %s\n", inet_ntoa( routingTable[i].destination ));
      /* just delete it */
      for(j=i; j<routingTableSize-1; j++)
	routingTable[j] = routingTable[j+1];
      routingTableSize--;
    }
  }
}

/* process incoming RIP entries */
void processIncoming(RipEntry *ripEntries, int size, struct in_addr from, int time) {
  int i,j;
  int found;
  int foundRouting;

  for( i=0; i<size; i++) {
    found = 0; /* ignore advertisments for directly connected networks */
    for( j=0; j< attachedNetworkSize; j++)
      if( attachedNetworks[j].addr.s_addr == ripEntries[i].ip_address.s_addr )
	found = 1;
    
    foundRouting = 0;
    if(! found) { /* only process advertisments for not directly connected networks */
      for( j=0; j < routingTableSize; j++) {
	if( routingTable[j].destination.s_addr == ripEntries[i].ip_address.s_addr ) { /* existing entry */
	  /*	  puts(">>existing entry");*/
	  foundRouting = 1;
	  if( routingTable[j].hopDistance > ripEntries[i].hopDistance+1 ||
	      routingTable[j].nextHop.s_addr == from.s_addr ) { 
	    /* better path (or worse or equal path from the same direction) */
	    routingTable[j].hopDistance = ripEntries[i].hopDistance + 1;
	    routingTable[j].nextHop     = from;
	    routingTable[j].lastUpdate  = time; /* update time */
	  }	  
	}
      }
      if(! foundRouting ) { /* new entry */
	/*	puts(">>new entry"); */
	routingTable[routingTableSize].destination = ripEntries[i].ip_address;
	routingTable[routingTableSize].hopDistance = ripEntries[i].hopDistance + 1;
	routingTable[routingTableSize].nextHop     = from;
	routingTable[routingTableSize].lastUpdate  = time;
	routingTableSize++;
      }
    }
  }
}

/* see if we have new packets */
void checkIncoming(int time, struct in_addr localAddr) {
  int i;
  struct sockaddr_in addr;
  int addrLen;
  struct timeval tv;
  int retval;
  fd_set rfds;
      
  char mesgBuf[66000];
  int mesgSize;
  Rip rip;
  int j, ripEntryNumber, found;

  
    for(i=0; i < attachedNetworkSize; i++) {  /* for all attached networks (needed?)*/
  do { /* process all incoming packets */
    /* Watch sockfd to see when it has input. */
    FD_ZERO(&rfds);
    FD_SET(attachedNetworks[i].sockfd, &rfds);
    /* Wait up to 10 usec seconds. */
    tv.tv_sec = 0;
    tv.tv_usec = 10;
    
    retval = select(attachedNetworks[i].sockfd+1, &rfds, NULL, NULL, &tv);
    /* Don't rely on the value of tv now! */
    
    addrLen = sizeof(addr);
    
    if (retval) {
      mesgSize = recvfrom(attachedNetworks[i].sockfd, mesgBuf, MAX_BUF, 
			  0, (struct sockaddr*) &addr, &addrLen);
      
      /* msg_waitall to wait for the whole packet */
      if( mesgSize > 0 && errno != EAGAIN) { /* ok, we found sth. */
	rip = *((Rip *)mesgBuf);
	
	printf("Received packet from %s ", inet_ntoa(rip.from));
	printf("to %s ", inet_ntoa(rip.to));
	printf("(really    %s)\n", inet_ntoa(addr.sin_addr));
	
	ripEntryNumber = (mesgSize-RIP_HEADER_SIZE)/sizeof(RipEntry);
	for(j=0; j< ripEntryNumber; j++) {
	  printf("network %s    hops %ld\n", inet_ntoa( rip.ripEntries[j].ip_address) , rip.ripEntries[j].hopDistance);	    
	}
	found = 0; /* false */
	for(j=0; j<attachedNetworkSize ; j++) { 
	  /* we may get messages from "unattached" networks, when simulating on one host */
	  if( rip.to.s_addr == attachedNetworks[j].addr.s_addr)
	    found = 1;
	}
	if( localAddr.s_addr != rip.from.s_addr && found)
	  /* ignore own advertisements and only take advertisements from "attached" networks */
	  processIncoming(rip.ripEntries, ripEntryNumber, 
			  rip.from, time);
      }
    } else puts("nothing on the network");
  } while(retval);
  } 
}

/* broadcast routing information on all networks (i. e. multicast groups) */
void doBroadcast(struct in_addr localAddr) {
  struct sockaddr_in cliAddr;
  int sockfd;

  int i;
  Rip ripPacket;

  /* build the packet */
  ripPacket.cmd       = RIPCMD_RESPONSE;
  ripPacket.vers      = 1;         /* rip v1 */
  ripPacket.rest[1]   = 0;         /* must be zero */
  ripPacket.rest[2]   = 0;         /* must be zero */
  ripPacket.from      = localAddr; /* inform the other where it is from 
				      to simulate several "routers" on one host */

  /* Always promote directly attached networks */
  for(i=0; i < attachedNetworkSize; i++) {
    ripPacket.ripEntries[i].netFamily = AF_INET;   /* Internet Adresses */
    ripPacket.ripEntries[i].pad = 0;
    ripPacket.ripEntries[i].ip_address = attachedNetworks[i].addr;
    ripPacket.ripEntries[i].pad1 = 0;            /* must be zero */
    ripPacket.ripEntries[i].pad2 = 0;            /* must be zero */
    ripPacket.ripEntries[i].hopDistance              = 1;
  }
  
  /* and the whole table */
  for(i=0; i < routingTableSize; i++) {
    ripPacket.ripEntries[i].netFamily = AF_INET;   /* Internet Adresses */
    ripPacket.ripEntries[i].pad = 0;
    ripPacket.ripEntries[attachedNetworkSize+i].ip_address = 
      routingTable[i].destination;
    ripPacket.ripEntries[attachedNetworkSize+i].pad1 = 0; /* must be zero */
    ripPacket.ripEntries[attachedNetworkSize+i].pad2 = 0; /* must be zero */
    ripPacket.ripEntries[attachedNetworkSize+i].hopDistance 
      = routingTable[i].hopDistance;
  }
  
  for(i=0; i < attachedNetworkSize; i++) {

    /* Open a UDP socket */
    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0 )) < 0){
      perror("Can't open socket");
      exit(-1);
    }
    
    printf("sending %i (to %s), size: %d\n",i,inet_ntoa(*((struct in_addr*)&attachedNetworks[i].addr)), RIP_HEADER_SIZE+sizeof(RipEntry)*(routingTableSize+attachedNetworkSize) );

    /* setup "multicast" address */
    memset((char *) &cliAddr, 0, sizeof(struct sockaddr_in)); 
    cliAddr.sin_family      = AF_INET;
    cliAddr.sin_addr        = attachedNetworks[i].addr;
    cliAddr.sin_port        = htons(DEFAULT_MULTICAST_PORT);

    ripPacket.to = attachedNetworks[i].addr; /* no chance to get this otherwise (on solaris/linux) */

    /* send message to "subnet" */
    if( sendto( sockfd, (char *) &ripPacket, 
		RIP_HEADER_SIZE+sizeof(RipEntry)*(routingTableSize+attachedNetworkSize), 
		/* RIP_HEADER_SIZE byte (=header) + 
		   sizeof(RipEntry) bytes * number (=body) */
		0, (struct sockaddr *) &cliAddr, sizeof(cliAddr) ) == -1 ) {
      perror("sendto error!");
      exit(-1);
    }

    close(sockfd);
  }
}

/* check user input from console */
void checkInput() {
  char buf[100];
  char buf1[100], buf2[100];
  fd_set rfds;
  int i;
  struct in_addr addr;
  int found; 
  
  struct timeval tv;
  int retval;
 
  /* Watch stdin (fd 0) to see when it has input. */
  FD_ZERO(&rfds);
  FD_SET(0, &rfds);
  /* Wait up to 10 usec seconds. */
  tv.tv_sec = 0;
  tv.tv_usec = 10;
  
  retval = select(1, &rfds, NULL, NULL, &tv);
  /* Don't rely on the value of tv now! */
  
  if (retval) {
    /* FD_ISSET(0, &rfds) will be true. */

  
    fgets(buf,100, stdin);
    if( strncmp(buf, "add", 3) == 0) {
      sscanf(buf, "%s %s\n", buf1, buf2);
      printf("Adding %s\n", buf2);

      found = 0;
      /* delete perhaps existing routes */
      for( i=0; i< routingTableSize; i++) {
	if( routingTable[i].destination.s_addr == inet_addr(buf2))
	  /*	  routingTable[i].lastUpdate = -10;*/
	  found = 1;
	if( found && i+1 < routingTableSize)
	  routingTable[i] = routingTable[i+1];
      }
      if( found )
	routingTableSize--;
	
      
      attachedNetworks[attachedNetworkSize].addr.s_addr = inet_addr( buf2 );
      if( (attachedNetworks[attachedNetworkSize].sockfd = 
	  attach( inet_addr( buf2 ), htons(DEFAULT_MULTICAST_PORT) )) < 0) {
	perror("Error joining multicast group");
	exit(0);
      }
      attachedNetworkSize++;
    } else if( strncmp(buf, "del", 3) == 0 ) {
      sscanf(buf, "%s %s\n", buf1, buf2);
      addr.s_addr = inet_addr( buf2 );
      
      found = 0;
      for( i=0; i<attachedNetworkSize; i++) {
	if( attachedNetworks[i].addr.s_addr == addr.s_addr )
	  found = 1;
	if( found && i+1<attachedNetworkSize)
	  attachedNetworks[i] = attachedNetworks[i+1];
      }
      if( found ) {
	attachedNetworkSize--;
	printf("deleted %s", inet_ntoa( addr ));
      } else
	printf("%s not found", inet_ntoa( addr ));
      
    }
  }
}

int main(int argc, char *argv[] ) {
  int time = 0;
  int i;
  struct hostent *thishost;
  char name[BUFLEN];

  if( argc > 1) { /* user real IP, if no other provided */
    localAddr.s_addr = inet_addr(argv[1]);
  } else {
    gethostname(name, BUFLEN);
    thishost=gethostbyname(name);
    
    memcpy((void *)&localAddr.s_addr, thishost->h_addr_list[0],
	   sizeof(long)); 
  }
  printf("local addr: %s", inet_ntoa(localAddr));

  puts("Commands: add/del MULTICAST-Addr (e.g. 230.0.0.1)");

  /* init */
  routingTableSize = 0;
  attachedNetworkSize = 0;
  
  for(;;) { /* forever */
    printf("***************** new round (%d) *******************\n", time);
    /* check user input */
    checkInput();

    /* check incoming packets */
    checkIncoming(time, localAddr);
   
    /* check timeouts */
    checkTimeouts(time);

    /* do broadcast */
    if( time%RIP_BROADCAST_TIME == 0 ) {
      doBroadcast(localAddr);
    }

    /* output routing table */
    puts("Routing table:");
    puts("network : hops : router-ip : last-updated");
    for(i=0; i< attachedNetworkSize; i++) {
      printf("* %s : 1 : ", inet_ntoa(attachedNetworks[i].addr) );
      printf("(%s) \n", inet_ntoa(localAddr));      
    }
    for(i=0; i< routingTableSize; i++) {
      printf("%s  : %ld : ", inet_ntoa(routingTable[i].destination), 
	     routingTable[i].hopDistance);
      printf("%s : %i\n", inet_ntoa(routingTable[i].nextHop), routingTable[i].lastUpdate);
    }
	     
    time++;
    sleep(SLEEP_INTERVALL);
  }

  return 0;
}
