/*
 * A sample client for the bgpmon server
 *  
 * bgpmonclient server-ip server-port type [peer-ip peer-as] > filename
 * 
 * Will write stream of MRTs to the specified file,
 * creating a log on the client's disc.  
 * You should be able to bgpdump the resulting stream of mrts.
 * 
 * The required parameters are:
 * 
 * server-ip = ip address of bgpmon server
 * server-port = port number for bgpmon server
 * type = 12 for table dump, 16 for updates
 * 
 * The optional parameters are used to filter the request:
 * 
 * peer-ip = ip address of a bgp peer monitored by bgpmon
 * peer-as = as number of the bgp peer
 * 
 * There are 4 modes of operation.  
 * 
 * 1) Required parameters with type=12 returns a list of
 * the bgp peers of the bgpmon server.  
 * This mode returns a series of control mrts with type=NULL 
 * and the as/ip information for a peer in the message.  
 * A control mrt with type=START preceeds the data and 
 * a control mrt with type=DIE follows the data.  
 * The server closes the connection after the DIE mrt.
 * 
 * 2) Required/optional parameters with type=12 returns a
 * a list of table dump mrts for the specified peer.
 * A control mrt with type=START preceeds the data and 
 * a control mrt with type=DIE follows the data.  
 * The server closes the connection after the DIE mrt.
 *
 * 3) Required parameters with type=16 returns a continuous
 * stream of mrt updates for each bgp message monitored.
 * A control mrt with type=START preceeds the data.
 * 
 * 4) Required/optional parameters with type=16 returns
 * a continuous stream of mrt updates for each bgp message
 * monitored from a specific peer.
 * A control mrt with type=START preceeds the data.
 * 
 * Note for methods 3 and 4.
 * The server may close the connection if the client
 * becomes a bottleneck and the queue overflows.
 * Someday it may send a control MRT with type-DIE first, 
 * so you should always check and terminate when received.
 * 
 * Important:
 * If you wish to modify this client, remember that you
 * are monitoring a real-time stream and need to process 
 * it in real-time!
 * 
 * The requests are sent to the bgpmon server in a binary format
 * 
 * octet 1 = version (currently 1)
 * octets 2,3 = type
 * octets 4,5 = length of optional data (0 or 6)
 * octets 6,7 = optional peer AS
 * octets 8,9,10,11 = optional peer IP
 * 
 */
 
#include <stdio.h>      
#include <sys/socket.h> 
#include <arpa/inet.h>  
#include <stdlib.h>    
#include <string.h>     
#include <unistd.h>     
#include <errno.h>

void fatal(char *errorMessage)
{
    perror(errorMessage);
    exit(1);
}

ssize_t
readn( int fd, void *vptr, size_t n)
{
	/* Read N bytes from a socket.
	 */
	u_char 			*ptr;
	ptr = vptr;

	size_t 		nleft;
	ssize_t 	nread;
	nleft = n;
	while (nleft > 0)
	{
		if ((nread = read(fd, ptr, nleft)) < 0)
		{
			if (errno == EINTR)
				nread = 0;
			else
				return(-1);
		}
		else if (nread == 0) // EOF
			break;

		nleft -= nread;
		ptr += nread;
	}
	return (n - nleft); // return >= 0
}


int main(int argc, char *argv[])
{
    char *servIP = NULL; 
    char *servPort = NULL;                   
    char *type = NULL;   
    char *peerIP = NULL;    
    char *peerAS = NULL;             

    switch ( argc )
    {
    	case 6:
    		peerAS = argv[5];
    		peerIP = argv[4];
	    case 4:
 	   		type = argv[3];
 	   		servPort = argv[2];
 	   		servIP = argv[1];
 	   		break;
 	   	default:
 	   		fprintf(stderr, "Usage: %s <bgpmon IP> <bgpmon Port> <Type> [<peer IP> <peer AS>]\n", argv[0]);
				exit(1);
      	break;
    }

    /* Create a reliable, stream socket using TCP */
    int sock;                        
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        fatal("socket() failed");

    /* Construct the server address structure  and establish a connection */
    unsigned short echoServPort = atoi(servPort);     
    struct sockaddr_in echoServAddr; 
    memset(&echoServAddr, 0, sizeof(echoServAddr));     /* Zero out structure */
    echoServAddr.sin_family      = AF_INET;             /* Internet address family */
    echoServAddr.sin_addr.s_addr = inet_addr(servIP);   /* Server IP address */
    echoServAddr.sin_port        = htons(echoServPort); /* Server port */
    if (connect(sock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr)) < 0)
        fatal("connect() failed");

    /* Construct the request from command line arguments and send to the server */
    struct requestStruct
    {
    	u_int8_t 		version;
    	u_int16_t 	type;
    	u_int16_t		length;
    } request;
    
    struct filterStruct
    {
    	u_int16_t as;
    	in_addr_t	ip;
    } filter;
    
    request.version = 1;
    request.type = htons(atoi(type));
    if ( argc <= 4 )
    	request.length = 0;
    else
    {
    	request.length = htons(sizeof( filter ));
    	filter.as = htons(atoi(peerAS));
    	filter.ip = inet_addr(peerIP);
    }
    if ( send(sock, &request, sizeof(request), 0) != sizeof(request) )
    	fatal("send(request) sent a different number of bytes than expected");
    if ( request.length > 0 
    		&& send(sock, &filter, sizeof(filter), 0) != sizeof(filter) )
      fatal("send(filter) sent a different number of bytes than expected");

    /* Echo the response back from the server */
    enum 
    { 
    	msgSize = 4096
    };
    struct mrtStruct
    {
    	struct mrtHeader
    	{
    		u_int32_t 			timestamp;
    		u_int16_t 			type;
    		u_int16_t 			subtype;
    		u_int32_t				length;
    	} header;
    	unsigned char 		message[msgSize];
    } mrt;    	     
    
 	  unsigned int hdrLen, msgLen;      
    for ( ;; )
    {
        /* First get the header */
        hdrLen = sizeof(struct mrtHeader);
        if ( readn(sock, &mrt.header, hdrLen) != hdrLen )
          fatal("recv(header) failed");
        msgLen = ntohl(mrt.header.length);
        /* now get the rest of this message */
        if ( readn(sock, &mrt.message, msgLen) != msgLen)
        	fatal("recv(message) failed");
        /* got it all, now echo it out */
        fwrite(&mrt, 1, hdrLen+msgLen, stdout);
        /* mrt control type = die when complete */
        if (ntohs(mrt.header.type) == 2)
        	break;
    }

    close(sock);
    exit(0);
} 


