/*
 * Copyright (c) 2000 Columbia University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: (1) source code distributions
 * retain the above copyright notice and this paragraph in its entirety, (2)
 * distributions including binary code include the above copyright notice and
 * this paragraph in its entirety in the documentation or other materials
 * provided with the distribution, and (3) all advertising materials mentioning
 * features or use of this software display the following acknowledgement:
 * ``This product includes software developed by Columbia University
 * and its contributors.'' Neither the name of the University nor the names 
 * of its contributors may 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.
 *
 * Written by Ping Pan, Columbia University, 2000
 *
 * The author would like to thank Bell Labs for providing time and freedom 
 * to complete this work. Please forward bug fixes, enhancements and questions 
 * to pingpan@cs.columbia.edu.
 */

/*
 * a new user-interface command:
 * 
 * yestat  [-visfpe] [-d flow_id] [destination address]
 *
 *	-v 		get running-code version
 *	-i		get interface information
 *	-s		get a system summary
 *	-f		get a flow dump
 *	-p		get all pending flows
 *	-e		get traffic counters
 *
 *	-d [flow_id]	delete a flow (with an integer id)
 *	
 *	[destination address]:	The remote/local router that you want to
 *		get information from. If nothing typed, it will assume the
 *		command to for the local machine.
 *
 * Note: new features to add/delete will be added later.
 * 
 */

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <poll.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "yestat.h"
#include "rtp_yessir.h"
#include "intserv.h"

extern int optind;

char			*prog;				/* programe name */
int 			ys = -1;			/* yestat socket */
struct sockaddr		whereto;			/* space for dest */
char 			data_buf[YESTAT_MAXBUF];	/* data buffer */

u_int32_t		gethostip(char *host);
void			usage();
int			yestat_recv();


int
main(int argc, char **argv)
{
	register struct sockaddr_in *to = (struct sockaddr_in *)&whereto;
	register struct yestat_msg *msg = (struct yestat_msg *)&data_buf;
	struct pollfd poll_fd;
	register char *cp;
	register u_int32_t fid, in, poll_timeout = 3 * 1000;
	int rc, options = 0, retries = 3, on = 1; 

	if ((cp = strrchr(argv[0], '/')) != NULL)
		prog = cp + 1;
	else
		prog = argv[0];

	bzero(msg, sizeof(*msg));
	msg->more = YESTAT_SINGLE;
	msg->ack = YESTAT_ACK;
	msg->len = htonl(sizeof(*msg));

	while ((rc = getopt(argc, argv, "visfd:pe")) != EOF)
		switch (rc) {
		case 'v':
			options++;
			msg->type = YESTAT_GET_VER;
			break;

		case 'i':
			options++;
			msg->type = YESTAT_GET_IF;
			break;

		case 's':
			options++;
			msg->type = YESTAT_GET_SUM;
			break;

		case 'f':
			options++;
			msg->type = YESTAT_GET_FLOWDUMP;
			break;

		case 'd':
			options++;
			fid = atol(optarg);
			msg->type = YESTAT_DEL_FLOW;
			break;

		case 'p':
			options++;
			msg->type = YESTAT_GET_PENDING;
			break;

		case 'e':
			options++;
			msg->type = YESTAT_GET_COUNTER;
			break;

		default:
			usage();
		}

	if (!options)
		usage();

	/* create/set/bind a UDP socket for yestat.
	 */
	bzero(to, sizeof(*to));
	to->sin_family = PF_INET;
	to->sin_port = htons(YESSIR_STAT_PORT);
	to->sin_addr.s_addr = INADDR_ANY;
	to->sin_len = sizeof(struct sockaddr_in);

	if ((ys = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("cannot create socket");
		exit(1);
	}

	if (setsockopt(ys, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) {
		perror("cannot set sockopt");
		exit(1);
	}

	if (setsockopt(ys, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on))) {
		perror("cannot set sockopt");
		exit(1);
	}

	if (bind(ys, (struct sockaddr *)to, sizeof(*to))) { 
		perror("failed to bind");
		exit(1);
	}

	/* find the destination 
	 */
	if (optind < argc) {
		if (!(in = gethostip(argv[optind])))
			exit(1);
	}
	else if (!(in = gethostip(NULL)))
		exit(1);

	to->sin_addr.s_addr = in;

	/* send the request
	 */
	rc = sendto(ys, (char *)msg, sizeof(*msg), 0, 
					(struct sockaddr *)to, sizeof(*to));

	if (rc < 0) {
		perror("fail to send yestat message");
		exit(1);
	}

	/* wait for and process a reply: 
	 *    poll <retries> times, each for <poll_timeout>
	 * Note: need to consider network delay and loss.
	 */
	for (;;) {
		poll_fd.fd = ys;
		poll_fd.events = POLLIN | POLLRDNORM | POLLPRI | POLLRDBAND;
		poll_fd.revents = POLLIN | POLLRDNORM | POLLPRI | POLLRDBAND;

		rc = poll(&poll_fd, 1, poll_timeout);
		if (rc == 1) {
			if (yestat_recv())
				break;
		}
		else if (rc == 0) {
			if (retries == 0) {
				perror("yestat timeout");
				break;
			}
			else {
				retries--;
				continue;
			}
		}
		else { 		/* rc = -1 */
			fprintf(stderr, "fail to poll, error: %d", errno);
			break;
		}
	}

	exit(0);
}


/* find the destination:
 * <host> points to an input address string. If null, get the local (i.e., host)
 * ip address.
 * 
 * return:
 *	non-zero	IPv4 address
 *	0		cannot find anything
 */

u_int32_t
gethostip(char *host)
{
	register struct hostent *hp;
	char name[MAXHOSTNAMELEN];
	register int in;

	if (!host) {		/* local interface */
		host = (char *)&name;
		if (gethostname(host,MAXHOSTNAMELEN) == -1) {
			fprintf(stderr, "%s: cannot get the hostname\n", prog);
			return 0;
		}
	}

	if (*host == '\0') {
		fprintf(stderr, "%s: bad hostname %s\n", prog, host);
		return 0;
	}

	/* get from the console */
	in = inet_addr(host);
	if (in != -1)
		return in;

	/* get from DNS */
	hp = gethostbyname(host);
	if (hp == NULL) {
		fprintf(stderr, "%s: unknown host %s\n", prog, host);
		return 0;
	}
	if (hp->h_addrtype != AF_INET || hp->h_length != 4) {
		fprintf(stderr, "%s: bad host %s\n", prog, host);
		return 0;
	}

	return *(u_int32_t *)(hp->h_addr_list[0]);
}


void
usage()
{
	fprintf(stderr, "Usage: %s [-visfpe] [-d flow_id] [destination address] \n", prog);
	exit(1);
}


/* print out the reply 
 */


void
print_yestat_ver(struct yestat_msg *rep)
{
	struct yestat_version_string *msg;

	msg = (struct yestat_version_string *)rep;

	if (ntohl(msg->req.len) < 
			(msg->string_len + sizeof(msg->ver_string))) {

		fprintf(stderr, "message too short\n");
		exit(1);
	}

	printf("\n");
	printf("code version: %.*s\n", msg->string_len, msg->ver_string);
	printf("\n");
	return;
}


void
print_yestat_sum(struct yestat_msg *rep)
{
	register struct yestat_summary *msg = (struct yestat_summary *)rep;

	printf("\nYESSIR information summary:\n\n");
	printf("\tMax Supported Flows:\t%d\n", 
				(int)ntohl(msg->max_flow));
	printf("\tCurrent Flow Count:\t%d\n", 
				(int)ntohl(msg->curr_flow));
	printf("\tCurrent Pending Flows:\t%d\n", 
				(int)ntohl(msg->curr_pend_flow));
	printf("\tCurrent Interfaces:\t%d\n", 
				(int)ntohl(msg->total_if));
	printf("\n");

	return;
}


/* interface type to ascci */
int
iftoa(u_char type)
{
	if (type == IFT_PARA) {
		printf("(parallel-port), ");
		return -1;
	}
	else if (type == IFT_SLIP) {
		printf("(slip), ");
		return -1;
	}
	else if (type == IFT_PPP) {
		printf("(ppp), ");
		return -1;
	}
	else if (type == IFT_LOOP) {
		printf("(loopback), ");
		return 0;
	}
	else if (type == IFT_ETHER) {
		printf("(ethernet), ");
		return 0;
	}
	else if (type == 0) {
		printf("(unknown if), ");
		return -1;
	}
	printf("(if type %d), ", type);
	return 0;
}


/* convert IP address to a string, but not into a single buffer
 * copied from somewhere.. routed ?
 */
char *
naddr_ntoa(u_int32_t a)
{
#define NUM_BUFS 4
	static int bufno;
	static struct {
	    char    str[16];		/* xxx.xxx.xxx.xxx\0 */
	} bufs[NUM_BUFS];
	char *s;
	struct in_addr addr;

	addr.s_addr = a;
	s = strcpy(bufs[bufno].str, inet_ntoa(addr));
	bufno = (bufno+1) % NUM_BUFS;
	return s;
#undef NUM_BUFS
}


/* fancy display for a supporting tool... that's what you do to
 * fight hangovers while on an oversea vacation.
 */
void
print_yestat_if(struct yestat_msg *rep)
{
	struct yestat_if *msg = (struct yestat_if *)rep;
	struct yestat_if_info *ifmsg;
	u_long num, i;

	num = (ntohl(msg->req.len) - sizeof(*msg)) / sizeof(*ifmsg);

	printf("\nInterface status:\n\n");

	i = 0;
	ifmsg = (struct yestat_if_info *)(msg + 1);

	for (; i < num; i++, printf("\n"), ifmsg += 1) {

		printf(" index %d: ", (int)ntohl(ifmsg->index));

		if (ifmsg->state == YESTAT_RESV_ENABLE)
			printf("resv enabled, ");
		else if (ifmsg->state == YESTAT_RESV_DISABLE)
			printf("resv disable, ");
		else
			printf("unknown state, ");

		printf("%s ", ifmsg->name);

		if (iftoa(ifmsg->type))
			continue;

		printf("l2_hdrlen: %d,  ", ifmsg->l2_hdrlen);
		printf("link_bw: %d kbps,  ", (int)ntohl(ifmsg->link_bw)/1000);
		printf("\n\t");
		printf("reservable_bw: %d kBps,  ", 
					(int)ntohl(ifmsg->max_resv_bw)/1000);
		printf("available_bw: %d kBps,  ", 
					(int)ntohl(ifmsg->avail_bw)/1000);
		printf("\n\t");
		printf("interface addr: %s", 
					naddr_ntoa(ntohl(ifmsg->int_addr)));
		printf("/%s ",  naddr_ntoa(ntohl(ifmsg->int_mask)));
		printf("to %s ",  naddr_ntoa(ntohl(ifmsg->int_dstaddr)));
		printf("\n\t");
		printf("rx_pkt %d, rx_byte %d, rx_err %d, ",
			(int)ntohl(ifmsg->rx_pkt), 
			(int)ntohl(ifmsg->rx_byte), 
			(int)ntohl(ifmsg->rx_err));
		printf("tx_pkt %d, tx_byte %d, tx_err %d, ",
			(int)ntohl(ifmsg->tx_pkt), 
			(int)ntohl(ifmsg->tx_byte), 
			(int)ntohl(ifmsg->tx_err));
	}

	printf("\n");
	return;
}


void
print_out(struct yestat_out_info *out, int num)
{
	struct yestat_out_info *this_out;
	int i, ftype, serv;

	for (this_out = out, i=0; i < num; this_out += 1, i++) {

		printf("\n");
		printf("\tegress #%d: ", (i+1));

		printf("if-index %d, ", (int)ntohs(out->out_if));

		if (out->state == ntohs(YESTAT_RESV))
			printf("reserved, ");
		else if (out->state == ntohs(YESTAT_PENDING))
			printf("pending, ");
		else
			printf("not reserved, ");

		if (out->rhandle)
			printf("resv-handle: 0x%x, ", (int)ntohl(out->rhandle));

		printf("\n");
		printf("\t\t");

		printf("required resv: ");

		ftype = (int)ntohs(out->req_ftype);
		if (ftype == YESSIR_MB_FSPEC) {
			printf("measurement-based flowspec, ");
			printf("%d kBps", (int)ntohl(out->req_rate)/1000);
		}
		else if (ftype == YESSIR_IS_FSPEC) {
			printf("int-serv flowspec, ");
			printf("%d kBps", (int)ntohl(out->req_rate)/1000);
		}
		else if (ftype == YESSIR_DS_FSPEC) {
			printf("diff-serv flowspec, ");
			printf("%d kBps", (int)ntohl(out->req_rate)/1000);
		}
		else
			printf("unknown flowspec ");

		printf("\n");
		printf("\t\t");

		printf("effective resv: ");

		ftype = (int)ntohs(out->eff_ftype);
		if (ftype == YESSIR_MB_FSPEC) {
			printf("measurement-based flowspec, ");
			printf("%d kBps", (int)ntohl(out->eff_rate)/1000);
		}
		else if (ftype == YESSIR_IS_FSPEC) {
			printf("int-serv, ");

			serv = (int)ntohs(out->is_service);

			if (serv == IntServ_ControlledLoad)
				printf("control-load ");
			else	/* just kidding */
				printf("guaranteed-serv ");
				
			printf("%d kBps", (int)ntohl(out->eff_rate)/1000);
		}
		else if (ftype == YESSIR_DS_FSPEC) {
			printf("diff-serv flowspec, ");
			printf("%d kBps", (int)ntohl(out->eff_rate)/1000);
		}

		printf("\n");
	}

	return;
}


void
print_flow(struct yestat_flow_info *flow)
{
	printf("\n");
	printf("flow id 0x%x: ",  (int)ntohl(flow->fid));

	printf("%s/%d -> %s/%d, udp, ", 
		naddr_ntoa(ntohl(flow->sa)), (int)ntohs(flow->sp),
		naddr_ntoa(ntohl(flow->da)), (int)ntohs(flow->dp));
	printf("\n");

	printf("\t");
	printf("ingress: if-index %d, ", (int)ntohs(flow->in_if));
	if(flow->fhandle)
		printf("flow-handle: 0x%x, ", (int)ntohl(flow->fhandle));
	printf(" %d seconds to live\n", (int)ntohl(flow->max_lifetime) 
				- (int)ntohl(flow->curr_lifetime));

	return;
}


void
print_yestat_flow(struct yestat_msg *rep)
{
	struct yestat_flow *msg = (struct yestat_flow *)rep;
	struct yestat_flow_info *flow;
	struct yestat_out_info *out;
	int len = (int)ntohl(msg->req.len), n;

	len -= sizeof(*msg);
	flow = (struct yestat_flow_info *)(msg + 1);

	if (msg->req.more == YESTAT_SINGLE)
		printf("\nYESSIR flow status:\n\n");

	while (len) {

		n = ntohs(flow->num_out_if);
		len -= sizeof(*flow) + n * sizeof(*out);

		if (len < 0) {
			fprintf(stderr, "received a bad message\n");
			exit(1);
		}

		print_flow(flow);
		if (n)
			print_out((struct yestat_out_info *)(flow + 1), n);

		flow = (struct yestat_flow_info *)
			((char *)flow + sizeof(*flow) + n * sizeof(*out));
	}

	printf("\n");
	return;
}


void
print_yestat_pending(struct yestat_msg *rep)
{
	return;
}


void
print_yestat_counter(struct yestat_msg *rep)
{
	struct yestat_cnt *cnt;
	int refills, refill_failure, depletes;

	cnt = (struct yestat_cnt *)(rep + 1);

	printf("\nYESSIR statistics:\n\n");

	printf("\tReceived RTCP messages:\t\t %d\n", 
				(int)ntohl(cnt->rtcp_rx_pkt));
	printf("\tSent RTCP messages:\t\t %d\n", 
				(int)ntohl(cnt->rtcp_tx_pkt));
	printf("\tReceived messages to host:\t %d\n", 
				(int)ntohl(cnt->rx_host));
	printf("\tReceived multicast messages:\t %d\n", 
				(int)ntohl(cnt->rx_mcast));

	printf("\n");
	printf("\tReceived bad messages:\t\t %d\n", 
				(int)ntohl(cnt->rx_bad_pkt));
	printf("\tReceived incomplete messages:\t %d\n", 
				(int)ntohl(cnt->incomp_pkt));
	printf("\tReceived bad RTCP messages:\t %d\n", 
				(int)ntohl(cnt->bad_rtcp));
	printf("\tReceived invalid RTCP version:\t %d\n", 
				(int)ntohl(cnt->rtcp_bad_version));
	printf("\tReceived bad RTCP length:\t %d\n", 
				(int)ntohl(cnt->rtcp_bad_len));
	printf("\tReceived duplicated RTCP pt:\t %d\n", 
				(int)ntohl(cnt->rtcp_dup_pt));
	printf("\tReceived unknown RTCP payloads:\t %d\n", 
				(int)ntohl(cnt->rtcp_unknown_pt));

	printf("\n");
	printf("\tUnknown YESSIR objects:\t\t %d\n", 
				(int)ntohl(cnt->yessir_unknown_obj));
	printf("\tMissing YESSIR payloads:\t %d\n", 
				(int)ntohl(cnt->null_yessir));
	printf("\tMissing YESSIR objects:\t\t %d\n", 
				(int)ntohl(cnt->missing_yessir_obj));
	printf("\tUnknown YESSIR action:\t\t %d\n", 
				(int)ntohl(cnt->unknown_action));
	printf("\tUnknown YESSIR flowspec:\t %d\n", 
				(int)ntohl(cnt->unknown_ftype));
	printf("\tReceived rejected YESSIR:\t %d\n", 
				(int)ntohl(cnt->rx_rejected_yessir));
	printf("\tDestination unreachable:\t %d\n", 
				(int)ntohl(cnt->dst_unreach));
	printf("\tLooping messages:\t\t %d\n", 
				(int)ntohl(cnt->looping));
	printf("\tFailed to get memory:\t\t %d\n", 
				(int)ntohl(cnt->no_memory));
	printf("\tReservation attempt:\t\t %d\n",
					(int)ntohl(cnt->resv_attempt));
	printf("\tReservation success:\t\t %d\n",
					(int)ntohl(cnt->resv_success));

	printf("\n");
	/* very implementation dependent
	 */
	refills = ntohl(cnt->refill_flow_tab) + 
		ntohl(cnt->refill_oentry_tab) +
		ntohl(cnt->refill_time_tab) +
		ntohl(cnt->refill_pending_tab);

	refill_failure = ntohl(cnt->refill_flow_failed) +
		ntohl(cnt->refill_oentry_failed) +
		ntohl(cnt->refill_time_failed) +
		ntohl(cnt->refill_pending_failed);

	depletes = ntohl(cnt->deplete_flow_tab) + 
		ntohl(cnt->deplete_oentry_tab) +
		ntohl(cnt->deplete_tentry_tab) +
		ntohl(cnt->deplete_pentry_tab);

	printf("\tTimes to refill buckets:\t %d\n", 
							refills);
	printf("\tTimes to refill failure:\t %d\n", 
							refill_failure);
	printf("\tTimes to depleting buckets:\t %d\n", 
							depletes);

	printf("\tCreate flow failure:\t\t %d\n",
					(int)ntohl(cnt->add_flow_failed));
	printf("\tCreate soft-state failure:\t %d\n",
					(int)ntohl(cnt->add_tentry_failed));
	printf("\tRemove nonexist entry:\t\t %d\n",
					(int)ntohl(cnt->del_nonexist_flow));
	printf("\tCannot queue pending flows:\t %d\n",
					(int)ntohl(cnt->no_pending_bucket));
	printf("\tPending flows out of sync:\t %d\n",
					(int)ntohl(cnt->no_pending_entry));
	printf("\tTimer out-of-sync:\t\t %d\n",
					(int)ntohl(cnt->timer_outsync));
	
	printf("\n");
	return;
}


/* receive yestat replies off a socket:
 * return:
 *	-1	complete the receive
 *	0	need to receive more (more than one fragment)
 */

struct printer_tab {
	int	type;
	void	(*printer_fcn)(struct yestat_msg *);
} printer_tab[] = {
	{ YESTAT_GET_VER, print_yestat_ver },
	{ YESTAT_GET_SUM, print_yestat_sum },
	{ YESTAT_GET_IF, print_yestat_if },
	{ YESTAT_GET_FLOWDUMP, print_yestat_flow },
	{ YESTAT_GET_PENDING, print_yestat_pending },
	{ YESTAT_GET_COUNTER, print_yestat_counter },
};

int
yestat_recv()
{
	struct yestat_msg *msg = (struct yestat_msg *)&data_buf;
	struct sockaddr_in from;
	int fromlen = sizeof(struct sockaddr_in); 
	u_int32_t i;
	int cc;

	bzero(msg, sizeof(*msg));

	if ((cc = recvfrom(ys, msg, YESTAT_MAXBUF, 0, 
				(struct sockaddr *)&from, &fromlen)) < 0) {

		fprintf(stderr, "fail to receive yestat message\n");
		exit(1);
	}

	if (cc < (int)ntohl(msg->len)) {
		fprintf(stderr, "message too short\n");
		exit(1);
	}

	if (msg->ack == YESTAT_NACK) {
		fprintf(stderr, "unsupported option at destination\n");
		exit(1);
	}

	for (i = 0; i < sizeof printer_tab / sizeof printer_tab[0]; i++)
		if (msg->type == printer_tab[i].type) {
			printer_tab[i].printer_fcn(msg);

			if (msg->more == YESTAT_MORE)
				return 0;
			else
				return -1;
		}

	fprintf(stderr, "received an unknown reply\n");
	exit(1);
}

