/*
 * This file is part of the UCB release of Plan 9. It is subject to the license
 * terms in the LICENSE file found in the top-level directory of this
 * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
 * part of the UCB release of Plan 9, including this file, may be copied,
 * modified, propagated, or distributed except according to the terms contained
 * in the LICENSE file.
 */

#include <u.h>
#include <lib9.h>
#include <ctype.h>
#include <bio.h>
#include <ndb.h>
#include <ip.h>
#include "icmp.h"

enum{
	Maxstring=	128,
	Maxpath=	256,
};

typedef struct DS DS;
struct DS {
	/* dial string */
	char	buf[Maxstring];
	char	*netdir;
	char	*proto;
	char	*rem;
};

char *argv0;
int debug;

void	histogram(int32_t *t, int n, int buckets, int32_t lo,
		      int32_t hi);

void
usage(void)
{
	fprint(2,
"usage: %s [-n][-a tries][-h buckets][-t ttl][-x net] [protocol!]destination\n",
		argv0);
	exits("usage");
}

static int
csquery(DS *ds, char *clone, char *dest)
{
	int n, fd;
	char *p, buf[Maxstring];

	/*
	 *  open connection server
	 */
	snprint(buf, sizeof(buf), "%s/cs", ds->netdir);
	fd = open(buf, ORDWR);
	if(fd < 0){
		if(!isdigit(*dest)){
			werrstr("can't translate");
			return -1;
		}

		/* no connection server, don't translate */
		snprint(clone, sizeof(clone), "%s/%s/clone", ds->netdir, ds->proto);
		strcpy(dest, ds->rem);
		return 0;
	}

	/*
	 *  ask connection server to translate
	 */
	sprint(buf, "%s!%s", ds->proto, ds->rem);
	if(write(fd, buf, strlen(buf)) < 0){
		close(fd);
		return -1;
	}

	/*
	 *  get an address.
	 */
	seek(fd, 0, 0);
	n = read(fd, buf, sizeof(buf) - 1);
	close(fd);
	if(n <= 0){
		werrstr("problem with cs");
		return -1;
	}

	buf[n] = 0;
	p = strchr(buf, ' ');
	if(p == 0){
		werrstr("problem with cs");
		return -1;
	}

	*p++ = 0;
	strcpy(clone, buf);
	strcpy(dest, p);
	return 0;
}

/*
 *  call the dns process and have it try to resolve the mx request
 */
static int
dodnsquery(DS *ds, char *ip, char *dom)
{
	char *p;
	Ndbtuple *t, *nt;

	p = strchr(ip, '!');
	if(p)
		*p = 0;

	t = dnsquery(ds->netdir, ip, "ptr");
	for(nt = t; nt != nil; nt = nt->entry)
		if(strcmp(nt->attr, "dom") == 0){
			strcpy(dom, nt->val);
			ndbfree(t);
			return 0;
		}
	ndbfree(t);
	return -1;
}

/*  for connection oriented protocols (il, tcp) we just need
 *  to try dialing.  resending is up to it.
 */
static int
tcpilprobe(int cfd, int dfd, char *dest, int interval)
{
	int n;
	char msg[Maxstring];

	USED(dfd);

	n = snprint(msg, sizeof msg, "connect %s", dest);
	alarm(interval);
	n = write(cfd, msg, n);
	alarm(0);
	return n;
}

/*
 *  for udp, we keep sending to an improbable port
 *  till we timeout or someone complains
 */
static int
udpprobe(int cfd, int dfd, char *dest, int interval)
{
	int n, i, rv;
	char msg[Maxstring];
	char err[Maxstring];

	seek(cfd, 0, 0);
	n = snprint(msg, sizeof msg, "connect %s", dest);
	if(write(cfd, msg, n)< 0)
		return -1;

	rv = -1;
	for(i = 0; i < 3; i++){
		alarm(interval/3);
		if(write(dfd, "boo hoo ", 8) < 0)
			break;
		/*
		 *  a hangup due to an error looks like 3 eofs followed
		 *  by a real error.  this is a qio.c qbread() strangeness
		 *  done for pipes.
		 */
		do {
			n = read(dfd, msg, sizeof(msg)-1);
		} while(n == 0);
		alarm(0);
		if(n > 0){
			rv = 0;
			break;
		}
		errstr(err, sizeof err);
		if(strstr(err, "alarm") == 0){
			werrstr(err);
			break;
		}
		werrstr(err);
	}
	alarm(0);
	return rv;
}

#define MSG "traceroute probe"
#define MAGIC (0xdead)&0xFF

/* ICMPv4 only */
static int
icmpprobe(int cfd, int dfd, char *dest, int interval)
{
	int x, i, n, len, rv;
	char buf[512], err[Maxstring], msg[Maxstring];
	Icmphdr *ip;

	seek(cfd, 0, 0);
	n = snprint(msg, sizeof msg, "connect %s", dest);
	if(write(cfd, msg, n)< 0)
		return -1;

	rv = -1;
	ip = (Icmphdr *)(buf + IPV4HDR_LEN);
	for(i = 0; i < 3; i++){
		alarm(interval/3);
		ip->type = EchoRequest;
		ip->code = 0;
		strcpy((char*)ip->data, MSG);
		ip->seq[0] = MAGIC;
		ip->seq[1] = MAGIC>>8;
		len = IPV4HDR_LEN + ICMP_HDRSIZE + sizeof(MSG);

		/* send a request */
		if(write(dfd, buf, len) < len)
			break;

		/* wait for reply */
		n = read(dfd, buf, sizeof(buf));
		alarm(0);
		if(n < 0){
			errstr(err, sizeof err);
			if(strstr(err, "alarm") == 0){
				werrstr(err);
				break;
			}
			werrstr(err);
			continue;
		}
		x = (ip->seq[1]<<8) | ip->seq[0];
		if(n >= len && ip->type == EchoReply && x == MAGIC &&
		    strcmp((char*)ip->data, MSG) == 0){
			rv = 0;
			break;
		}
	}
	alarm(0);
	return rv;
}

static void
catch(void *a, char *msg)
{
	USED(a);
	if(strstr(msg, "alarm"))
		noted(NCONT);
	else
		noted(NDFLT);
}

static int
call(DS *ds, char *clone, char *dest, int ttl, int32_t *interval)
{
	int cfd, dfd, rv, n;
	char msg[Maxstring];
	char file[Maxstring];
	int64_t start;

	notify(catch);

	/* start timing */
	start = nsec()/1000;
	rv = -1;

	cfd = open(clone, ORDWR);
	if(cfd < 0){
		werrstr("%s: %r", clone);
		return -1;
	}
	dfd = -1;

	/* get conversation number */
	n = read(cfd, msg, sizeof(msg)-1);
	if(n <= 0)
		goto out;
	msg[n] = 0;

	/* open data file */
	sprint(file, "%s/%s/%s/data", ds->netdir, ds->proto, msg);
	dfd = open(file, ORDWR);
	if(dfd < 0)
		goto out;

	/* set ttl */
	if(ttl)
		fprint(cfd, "ttl %d", ttl);

	/* probe */
	if(strcmp(ds->proto, "udp") == 0)
		rv = udpprobe(cfd, dfd, dest, 3000);
	else if(strcmp(ds->proto, "icmp") == 0)
		rv = icmpprobe(cfd, dfd, dest, 3000);
	else	/* il and tcp */
		rv = tcpilprobe(cfd, dfd, dest, 3000);
out:
	/* turn off alarms */
	alarm(0);
	*interval = nsec()/1000 - start;
	close(cfd);
	close(dfd);
	return rv;
}

/*
 *  parse a dial string.  default netdir is /net.
 *  default proto is tcp.
 */
static void
dial_string_parse(char *str, DS *ds)
{
	char *p, *p2;

	strncpy(ds->buf, str, Maxstring);
	ds->buf[Maxstring-3] = 0;

	p = strchr(ds->buf, '!');
	if(p == 0) {
		ds->netdir = 0;
		ds->proto = "tcp";
		ds->rem = ds->buf;
	} else {
		if(*ds->buf != '/'){
			ds->netdir = 0;
			ds->proto = ds->buf;
		} else {
			for(p2 = p; *p2 != '/'; p2--)
				;
			*p2++ = 0;
			ds->netdir = ds->buf;
			ds->proto = p2;
		}
		*p = 0;
		ds->rem = p + 1;
	}
	if(strchr(ds->rem, '!') == 0)
		strcat(ds->rem, "!32767");
}

void
main(int argc, char **argv)
{
	int buckets, ttl, j, done, tries, notranslate;
	uintptr_t lo, hi, sum, x;
	int32_t *t;
	char *net, *p;
	char clone[Maxpath], dest[Maxstring], hop[Maxstring], dom[Maxstring];
	char err[Maxstring];
	DS ds;

	buckets = 0;
	tries = 3;
	notranslate = 0;
	net = "/net";
	ttl = 1;
	ARGBEGIN{
	case 'a':
		tries = atoi(EARGF(usage()));
		break;
	case 'd':
		debug++;
		break;
	case 'h':
		buckets = atoi(EARGF(usage()));
		break;
	case 'n':
		notranslate++;
		break;
	case 't':
		ttl = atoi(EARGF(usage()));
		break;
	case 'x':
		net = EARGF(usage());
		break;
	default:
		usage();
	}ARGEND;

	if(argc < 1)
		usage();

	t = malloc(tries*sizeof(uintptr_t));

	dial_string_parse(argv[0], &ds);

	if(ds.netdir == 0)
		ds.netdir = net;
	if(csquery(&ds, clone, dest) < 0){
		fprint(2, "%s: %s: %r\n", argv0, argv[0]);
		exits(0);
	}
	print("trying %s/%s!%s\n\n", ds.netdir, ds.proto, dest);
	print("                       round trip times in µs\n");
	print("                        low      avg     high\n");
	print("                     --------------------------\n");

	done = 0;
	for(; ttl < 32; ttl++){
		for(j = 0; j < tries; j++){
			if(call(&ds, clone, dest, ttl, &t[j]) >= 0){
				if(debug)
					print("%ld %s\n", t[j], dest);
				strcpy(hop, dest);
				done = 1;
				continue;
			}
			errstr(err, sizeof err);
			if(strstr(err, "refused")){
				strcpy(hop, dest);
				p = strchr(hop, '!');
				if(p)
					*p = 0;
				done = 1;
			} else if(strstr(err, "unreachable")){
				snprint(hop, sizeof(hop), "%s", err);
				p = strchr(hop, '!');
				if(p)
					*p = 0;
				done = 1;
			} else if(strncmp(err, "ttl exceeded at ", 16) == 0)
				strcpy(hop, err+16);
			else {
				strcpy(hop, "*");
				break;
			}
			if(debug)
				print("%ld %s\n", t[j], hop);
		}
		if(strcmp(hop, "*") == 0){
			print("*\n");
			continue;
		}
		lo = 10000000;
		hi = 0;
		sum = 0;
		for(j = 0; j < tries; j++){
			x = t[j];
			sum += x;
			if(x < lo)
				lo = x;
			if(x > hi)
				hi = x;
		}
		if(notranslate == 1 || dodnsquery(&ds, hop, dom) < 0)
			dom[0] = 0;
		/* don't truncate: ipv6 addresses can be quite long */
		print("%-18s %8ld %8ld %8ld %s\n", hop, lo, sum/tries, hi, dom);
		if(buckets)
			histogram(t, tries, buckets, lo, hi);
		if(done)
			break;
	}
	exits(0);
}

char *order = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

void
histogram(int32_t *t, int n, int buckets, int32_t lo, int32_t hi)
{
	int i, j, empty;
	int32_t span;
	static char *bar;
	char *p;
	char x[64];

	if(bar == nil)
		bar = malloc(n+1);

	print("+++++++++++++++++++++++\n");
	span = (hi-lo)/buckets;
	span++;
	empty = 0;
	for(i = 0; i < buckets; i++){
		p = bar;
		for(j = 0; j < n; j++)
			if(t[j] >= lo+i*span && t[j] <= lo+(i+1)*span)
				*p++ = order[j];
		*p = 0;
		if(p != bar){
			snprint(x, sizeof x, "[%ld-%ld]", lo+i*span, lo+(i+1)*span);
			print("%-16s %s\n", x, bar);
			empty = 0;
		} else if(!empty){
			print("...\n");
			empty = 1;
		}
	}
	print("+++++++++++++++++++++++\n");
}
