jehanne/sys/src/cmd/ip/traceroute.c

493 lines
9.2 KiB
C

/*
* 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 <chartypes.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 = sys_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(jehanne_write(fd, buf, strlen(buf)) < 0){
sys_close(fd);
return -1;
}
/*
* get an address.
*/
sys_seek(fd, 0, 0);
n = jehanne_read(fd, buf, sizeof(buf) - 1);
sys_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);
sys_alarm(interval);
n = jehanne_write(cfd, msg, n);
sys_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];
sys_seek(cfd, 0, 0);
n = snprint(msg, sizeof msg, "connect %s", dest);
if(jehanne_write(cfd, msg, n)< 0)
return -1;
rv = -1;
for(i = 0; i < 3; i++){
sys_alarm(interval/3);
if(jehanne_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 = jehanne_read(dfd, msg, sizeof(msg)-1);
} while(n == 0);
sys_alarm(0);
if(n > 0){
rv = 0;
break;
}
sys_errstr(err, sizeof err);
if(strstr(err, "alarm") == 0){
werrstr(err);
break;
}
werrstr(err);
}
sys_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;
sys_seek(cfd, 0, 0);
n = snprint(msg, sizeof msg, "connect %s", dest);
if(jehanne_write(cfd, msg, n)< 0)
return -1;
rv = -1;
ip = (Icmphdr *)(buf + IPV4HDR_LEN);
for(i = 0; i < 3; i++){
sys_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(jehanne_write(dfd, buf, len) < len)
break;
/* wait for reply */
n = jehanne_read(dfd, buf, sizeof(buf));
sys_alarm(0);
if(n < 0){
sys_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;
}
}
sys_alarm(0);
return rv;
}
static void
catch(void *a, char *msg)
{
USED(a);
if(strstr(msg, "alarm"))
sys_noted(NCONT);
else
sys_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;
sys_notify(catch);
/* start timing */
start = nsec()/1000;
rv = -1;
cfd = sys_open(clone, ORDWR);
if(cfd < 0){
werrstr("%s: %r", clone);
return -1;
}
dfd = -1;
/* get conversation number */
n = jehanne_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 = sys_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 */
sys_alarm(0);
*interval = nsec()/1000 - start;
sys_close(cfd);
sys_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;
}
sys_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");
}