/* * 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 <libc.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(long *t, int n, int buckets, long lo, long hi); voidusage(void){ fprint(2,"usage: %s [-n][-a tries][-h buckets][-t ttl][-x net] [protocol!]destination\n", argv0); exits("usage");} static intcsquery(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 intdodnsquery(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 inttcpilprobe(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 intudpprobe(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 /* ICMPv4 only */static inticmpprobe(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 voidcatch(void *a, char *msg){ USED(a); if(strstr(msg, "alarm")) noted(NCONT); else noted(NDFLT);} static intcall(DS *ds, char *clone, char *dest, int ttl, long *interval){ int cfd, dfd, rv, n; char msg[Maxstring]; char file[Maxstring]; vlong 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 voiddial_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");} voidmain(int argc, char **argv){ int buckets, ttl, j, done, tries, notranslate; long lo, hi, sum, x; long *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(ulong)); 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"; voidhistogram(long *t, int n, int buckets, long lo, long hi){ int i, j, empty; long 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");}
/*
* 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 <libc.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(long *t, int n, int buckets, long lo, long 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
/* 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, long *interval)
{
int cfd, dfd, rv, n;
char msg[Maxstring];
char file[Maxstring];
vlong 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;
long lo, hi, sum, x;
long *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(ulong));
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(long *t, int n, int buckets, long lo, long hi)
{
int i, j, empty;
long 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");
}