/* $Id: viewer.c,v 0.7 2003/10/16 10:38:32 kjc Exp $ */
/*
 *  Copyright (c) 1996-2000
 *	Sony Computer Science Laboratories, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms of parts of or the
 * whole original or derived work are permitted provided that the above
 * copyright notice is retained and the original work is properly
 * attributed to the author. The name of the author may not be used to
 * endorse or promote products derived from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
/* viewer.c -- a viewer module for remote-monitoring.  this module is
   shared by tttview and ttttextview but TTT_TEXT flag is set for
   ttttextview. */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <netdb.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "ttt.h"
#include "ttt_node.h"
#include "ttt_remote.h"
#include "ttt_tk.h"

#define BUFFER_SIZE	4096	/* big enough */
static char buffer[BUFFER_SIZE];	/* receive buffer */

static struct sockaddr_in probe_addr;
static char *probe_name = NULL;
static int packets_received;
static struct timeval remote_time;
static int shared_port = 1;		/* share the multicast port */
static int multicast = 0;		/* use multicast */

void view_sockread(ClientData clientdata, int mask);
static int check_seqno(int seq_no);
static int read_record(struct ttt_record *trp);

double get_remotetime(void);

extern char *pcap_lookupdev(char *errbuf);
extern int pcap_lookupnet(char *device, u_long *netp, u_long *maskp, char *errbuf);

static void usage()
{
    printf("usage: tttview [options]\n");
    printf(" options:\n");
    printf("	[-multicast]\n");
    printf("    [-addr recv_addr]\n");
    printf("    [-mcastifaddr mcast_if_addr]\n");
    printf("	[-port recv_port]\n");
    printf("	[-probe probe_addr]\n");
    printf("    [-yscale 'K'|'M'|n]\n");
    exit(1);
}

void view_parseargs(int argc, char **argv)
{
    int i;

    for (i=1; i<argc; i++)
	if (strncmp(argv[i], "-multicast", 4) == 0)
	    ttt_viewname = TTT_MCASTADDR;
	else if (strncmp(argv[i], "-addr", 4) == 0 && ++i < argc)
	    ttt_viewname = argv[i];
	else if (strncmp(argv[i], "-mcastifaddr", 4) == 0 && ++i < argc)
	    ttt_mcastif = argv[i];
	else if (strncmp(argv[i], "-port", 4) == 0 && ++i < argc)
	    ttt_portno = atoi(argv[i]);
        else if (strncmp(argv[i], "-probe", 4) == 0 && ++i < argc)
	    probe_name = argv[i];
	else if (strcmp(argv[i], "-yscale") == 0 && ++i < argc) {
	    if (toupper(argv[i][0]) == 'K')
		ttt_yscale = 1000;
	    else if (toupper(argv[i][0]) == 'M')
		ttt_yscale = 1000000;
	    else
		ttt_yscale = strtol(argv[i], NULL, 0);
	}
	else if (strcmp(argv[i], "-help") == 0 ||
		 strcmp(argv[i], "--help") == 0 ||
		 strcmp(argv[i], "-h") == 0)
	    usage();
	else if (strncmp(argv[i], "-version", 4) == 0) {
	    printf("%s\n", ttt_version);
	    exit(0);
	}
}

int view_opensock(void)
{
    char my_name[MAXHOSTNAMELEN];
    struct sockaddr_in my_addr;
    int sockfd;
    
    if (ttt_viewname == NULL) {
	/* use my host name */
	if (gethostname(my_name, MAXHOSTNAMELEN) == -1)
	    fatal_error("no host_name");
    }
    else
	strcpy(my_name, ttt_viewname);
    if (name2sockaddrin(my_name, ttt_portno, &my_addr) < 0)
	fatal_error("can't get my address!");

#ifdef IN_MULTICAST
    if (IN_MULTICAST(ntohl(my_addr.sin_addr.s_addr)))
	multicast = 1;
#endif
    if ((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
	fatal_error("receiver: can't open socket");

    if (multicast) {
#ifdef IP_ADD_MEMBERSHIP
	struct ip_mreq mreq;
	struct sockaddr_in ifaddr;

	mreq.imr_multiaddr.s_addr = my_addr.sin_addr.s_addr;
	if (ttt_mcastif == NULL) {
	    mreq.imr_interface.s_addr = htonl(INADDR_ANY); /* use default if */
	}
	else {
	    if (name2sockaddrin(ttt_mcastif, ttt_portno, &ifaddr) < 0)
		fatal_error("can't get local address!");
	    mreq.imr_interface.s_addr = ifaddr.sin_addr.s_addr;
	}

	if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
		       (char *)&mreq, sizeof(mreq)) < 0)
	    fatal_error("can't join group");
	
	printf("joined multicast group: %s\n", ttt_viewname);
#else
	fatal_error("IP multicat not supported!");
#endif /* IP_ADD_MEMBERSHIP */
    }

    if (shared_port) {
	int one = 1;
	if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&one, sizeof(one)) < 0)
	    fatal_error("can't share the port");
	printf("port %d is shared.\n", ttt_portno);
    }

    if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr)) < 0)
	fatal_error("receiver: can't bind");

    if (!multicast)
	printf("viewer started. reading from %s:%d ....\n",
	       inet_ntoa(my_addr.sin_addr), ttt_portno);

    /* get local network address to look up local host names */
    {
	char *device;
	u_long localnet, netmask;
	struct in_addr inaddr;
	
	if ((device = pcap_lookupdev(buffer)) == NULL)
	    fatal_error(buffer);
	if (pcap_lookupnet(device, &localnet, &netmask, buffer) < 0)
	    fatal_error(buffer);
	netname_init(localnet, netmask);
	inaddr.s_addr = localnet;
	printf("local network is %s", inet_ntoa(inaddr));
	inaddr.s_addr = netmask;
	printf(" netmask is %s\n", inet_ntoa(inaddr));
    }

    if (probe_name != NULL) {
	if (name2sockaddrin(probe_name, 0, &probe_addr) < 0)
	    fatal_error("can't get probe address!");
	printf("reading from [%s] ....\n", probe_name);
    }
    else
	memset(&probe_addr, 0, sizeof(probe_addr));

    return sockfd;
}

void view_closesock(int sockfd)
{
#ifdef IP_ADD_MEMBERSHIP
    if (multicast) {
	char my_name[MAXHOSTNAMELEN];
	struct sockaddr_in my_addr;
	struct ip_mreq mreq;
	
	if (ttt_viewname == NULL) {
	    /* use my host name */
	    if (gethostname(my_name, MAXHOSTNAMELEN) == -1)
		fatal_error("no host_name");
	}
	else
	    strcpy(my_name, ttt_viewname);
	if (name2sockaddrin(my_name, ttt_portno, &my_addr) < 0)
	    fatal_error("can't get my address!");

	mreq.imr_multiaddr.s_addr = my_addr.sin_addr.s_addr;
	mreq.imr_interface.s_addr = htonl(INADDR_ANY);

	if (setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
		       (char *)&mreq, sizeof(mreq)) < 0)
	    perror("can't drop group");
    }
#endif /* IP_ADD_MEMBERSHIP */
    close(sockfd);
}

/* statistics info */
static u_long lost_packets;	/* total reports lost */
static u_long out_of_order;	/* total out of order report */

/* report of the pcap statistics at the probe */
static u_long pcap_stats_recv;  /* received packets */
static u_long pcap_stats_drop;	/* dropped packets */

int get_pcapstat(u_long *recvp, u_long *dropp, u_long *lostp)
{
    *recvp = pcap_stats_recv;
    *dropp = pcap_stats_drop;
    *lostp = lost_packets;
    return 0;
}

static int check_seqno(int seq_no)
{
    int lost_count;
    static u_long last_seqno; 
#ifndef TTT_TEXT
    char buf[128];
#endif
    
    if (seq_no == last_seqno+1) {
	/* normal case */
	last_seqno = seq_no;
	return 0;
    }
    else if (seq_no > last_seqno+1) {
	/* packet loss */
	lost_count = seq_no - last_seqno -1;
	if (last_seqno == 0) {
	    /* this is the first packet */
	    lost_packets = 0;
	}
	else {
	    lost_packets += lost_count;
#ifdef REMOTE_DEBUG
	    printf("[warning] lost %d packets\n", lost_count);
#endif
	}
	last_seqno = seq_no;
	return (lost_count);
    }
    else {
	/* out of order report */
	if (seq_no < 10 || (last_seqno - seq_no) > 100) {
	    /* seq_no gets too small. the probe must have restarted.  */
#ifdef TTT_TEXT
	    printf("[warning] probe seems to have restarted.\n");
#else
	    sprintf(buf, "probe[%s] seems to have restarted.",
		    inet_ntoa(probe_addr.sin_addr));
	    ttt_showmessage(buf);
#endif
	    last_seqno = seq_no;
	    lost_packets = out_of_order = 0;
	    return 9999;
	}
	out_of_order++;
#ifdef REMOTE_DEBUG
	printf("[warning] got out-of-order packet\n");
#endif
	return (-1);
    }
}


void view_sockread(ClientData clientdata, int mask)
{
    int sockfd, nbytes, fromlen, rsize, seq_no, nrecords, i;
    struct sockaddr_in from_addr;
    struct ttt_hdr *hdr;
    char *cp;

    sockfd = (int)clientdata;
    fromlen = sizeof(from_addr);
    if ((nbytes = recvfrom(sockfd, buffer, BUFFER_SIZE, 0,
			   (struct sockaddr *)&from_addr, &fromlen)) == -1) {
	perror("recvfrom");
	return;
    }

    if (probe_addr.sin_addr.s_addr == 0) {
	probe_addr = from_addr;
	printf("reading from probe [%s] ....\n",
	       inet_ntoa(from_addr.sin_addr));
    }

    /* is this the probe we are reading from? */
    if (from_addr.sin_addr.s_addr != probe_addr.sin_addr.s_addr) {
	static int warned = 0;
	if (!warned) {
	    printf("[warning] there are multiple probes.\n");
	    printf("\tignoring probe: %s\n",
		   inet_ntoa(from_addr.sin_addr));
	    warned = 1;
	}
	return;
    }

    if (nbytes < sizeof(struct ttt_hdr)) {
	printf("sockread: packet too short size=%d\n", nbytes);
	return;
    }
    
    hdr = (struct ttt_hdr *)buffer;
    if (ntohs(hdr->th_magic) != TTT_MAGIC) {
	printf("[warning] bad magic!\n");
	return;
    }

    pcap_stats_recv = ntohl(hdr->th_recvpkts);
    pcap_stats_drop = ntohl(hdr->th_droppkts);

    seq_no = ntohl(hdr->th_seqno);
    if (check_seqno(seq_no) < 0) 
	return;

    nrecords = ntohl(hdr->th_nrecords);
    remote_time.tv_sec = ntohl(hdr->th_tvsec);
    remote_time.tv_usec = ntohl(hdr->th_tvusec);

    cp = buffer + sizeof(struct ttt_hdr);
    for (i=0; i<nrecords; i++) {
	if ((rsize = read_record((struct ttt_record *)cp)) < 0)
	    break;
	cp += rsize;
    }

#ifdef TTT_TEXT
    ttt_textview(packets_received++);
#else
    /* call ttt_display to update the graph */
    ttt_display(packets_received++);
#endif
}

/* write node info to ttt_record */
static int read_record(struct ttt_record *trp)
{
    int size, rval;
    long type, id[4];
    struct ttt_record6 *tr6p;

    if (trp->tr_type != TTTTYPE_IPV6HOST) {
	type = ntohl(trp->tr_type);
	size = ntohl(trp->tr_size);
	id[0] = ntohl(trp->tr_id[0]);
#ifdef IPV6
	id[1] = 0;
	id[2] = 0;
	id[3] = 0;
#endif
	rval = sizeof(struct ttt_record);
    }
    else {
	tr6p = (struct ttt_record6 *)trp;
	type = ntohl(tr6p->tr_type);
	size = ntohl(tr6p->tr_size);
	id[0] = ntohl(tr6p->tr_id[0]);
#ifdef IPV6
	id[1] = ntohl(tr6p->tr_id[1]);
	id[2] = ntohl(tr6p->tr_id[2]);
	id[3] = ntohl(tr6p->tr_id[3]);
#endif
	rval = sizeof(struct ttt_record6);
    }

    node_record(type, id, size);
    
    return rval;
}

double get_timeindouble(void)
{
    double sec;
    static struct timeval start;
    static int first = 1;

    if (first) {
	start = remote_time;
	first = 0;
    }
    
    sec = (double)(remote_time.tv_sec - start.tv_sec)
	+ (double)(remote_time.tv_usec - start.tv_usec) / 1000000.0;
    return sec;
}



syntax highlighted by Code2HTML, v. 0.9.1