xref: /plan9/sys/src/cmd/ip/traceroute.c (revision ec59a3ddbfceee0efe34584c2c9981a5e5ff1ec4)
1 #include <u.h>
2 #include <libc.h>
3 #include <ctype.h>
4 #include <bio.h>
5 #include <ndb.h>
6 
7 enum{
8 	Maxstring=	128,
9 	Maxpath=	256,
10 };
11 
12 typedef struct DS DS;
13 struct DS {
14 	/* dist string */
15 	char	buf[Maxstring];
16 	char	*netdir;
17 	char	*proto;
18 	char	*rem;
19 };
20 
21 typedef struct Icmp Icmp;
22 struct Icmp
23 {
24 	uchar	vihl;		/* Version and header length */
25 	uchar	tos;		/* Type of service */
26 	uchar	length[2];	/* packet length */
27 	uchar	id[2];		/* Identification */
28 	uchar	frag[2];	/* Fragment information */
29 	uchar	ttl;		/* Time to live */
30 	uchar	proto;		/* Protocol */
31 	uchar	ipcksum[2];	/* Header checksum */
32 	uchar	src[4];		/* Ip source */
33 	uchar	dst[4];		/* Ip destination */
34 	uchar	type;
35 	uchar	code;
36 	uchar	cksum[2];
37 	uchar	icmpid[2];
38 	uchar	seq[2];
39 	uchar	data[1];
40 };
41 
42 enum
43 {			/* Packet Types */
44 	EchoReply	= 0,
45 	Unreachable	= 3,
46 	SrcQuench	= 4,
47 	EchoRequest	= 8,
48 	TimeExceed	= 11,
49 	Timestamp	= 13,
50 	TimestampReply	= 14,
51 	InfoRequest	= 15,
52 	InfoReply	= 16,
53 
54 	ICMP_IPSIZE	= 20,
55 	ICMP_HDRSIZE	= 8,
56 };
57 
58 char *argv0;
59 int debug;
60 
61 void	histogram(long *t, int n, int buckets, long lo, long hi);
62 
63 void
64 usage(void)
65 {
66 	fprint(2, "usage: %s [-n] [protocol!]destination\n", argv0);
67 	exits("usage");
68 }
69 
70 static int
71 csquery(DS *ds, char *clone, char *dest)
72 {
73 	int n, fd;
74 	char *p, buf[Maxstring];
75 
76 	/*
77 	 *  open connection server
78 	 */
79 	snprint(buf, sizeof(buf), "%s/cs", ds->netdir);
80 	fd = open(buf, ORDWR);
81 	if(fd < 0){
82 		if(!isdigit(*dest)){
83 			werrstr("can't translate");
84 			return -1;
85 		}
86 
87 		/* no connection server, don't translate */
88 		snprint(clone, sizeof(clone), "%s/%s/clone", ds->netdir, ds->proto);
89 		strcpy(dest, ds->rem);
90 		return 0;
91 	}
92 
93 	/*
94 	 *  ask connection server to translate
95 	 */
96 	sprint(buf, "%s!%s", ds->proto, ds->rem);
97 	if(write(fd, buf, strlen(buf)) < 0){
98 		close(fd);
99 		return -1;
100 	}
101 
102 	/*
103 	 *  get an address.
104 	 */
105 	seek(fd, 0, 0);
106 	n = read(fd, buf, sizeof(buf) - 1);
107 	close(fd);
108 	if(n <= 0){
109 		werrstr("problem with cs");
110 		return -1;
111 	}
112 
113 	buf[n] = 0;
114 	p = strchr(buf, ' ');
115 	if(p == 0){
116 		werrstr("problem with cs");
117 		return -1;
118 	}
119 
120 	*p++ = 0;
121 	strcpy(clone, buf);
122 	strcpy(dest, p);
123 	return 0;
124 }
125 
126 /*
127  *  call the dns process and have it try to resolve the mx request
128  */
129 static int
130 dodnsquery(DS *ds, char *ip, char *dom)
131 {
132 	char *p;
133 	Ndbtuple *t, *nt;
134 
135 	p = strchr(ip, '!');
136 	if(p)
137 		*p = 0;
138 
139 	t = dnsquery(ds->netdir, ip, "ptr");
140 	for(nt = t; nt != nil; nt = nt->entry)
141 		if(strcmp(nt->attr, "dom") == 0){
142 			strcpy(dom, nt->val);
143 			ndbfree(t);
144 			return 0;
145 		}
146 	ndbfree(t);
147 	return -1;
148 }
149 
150 /*  for connection oriented protocols (il, tcp) we just need
151  *  to try dialing.  resending is up to it.
152  */
153 static int
154 tcpilprobe(int cfd, int dfd, char *dest, int interval)
155 {
156 	int n;
157 	char msg[Maxstring];
158 
159 	USED(dfd);
160 
161 	n = snprint(msg, sizeof msg, "connect %s", dest);
162 	alarm(interval);
163 	n = write(cfd, msg, n);
164 	alarm(0);
165 	return n;
166 }
167 
168 /*
169  *  for udp, we keep sending to an improbable port
170  *  till we timeout or someone complains
171  */
172 static int
173 udpprobe(int cfd, int dfd, char *dest, int interval)
174 {
175 	int n, i, rv;
176 	char msg[Maxstring];
177 	char err[Maxstring];
178 
179 	seek(cfd, 0, 0);
180 	n = snprint(msg, sizeof msg, "connect %s", dest);
181 	if(write(cfd, msg, n)< 0)
182 		return -1;
183 
184 	rv = -1;
185 	for(i = 0; i < 3; i++){
186 		alarm(interval/3);
187 		if(write(dfd, "boo hoo ", 8) < 0)
188 			break;
189 		/*
190 		 *  a hangup due to an error looks like 3 eofs followed
191 		 *  by a real error.  this is a qio.c qbread() strangeness
192 		 *  done for pipes.
193 		 */
194 		do {
195 			n = read(dfd, msg, sizeof(msg)-1);
196 		} while(n == 0);
197 		alarm(0);
198 		if(n > 0){
199 			rv = 0;
200 			break;
201 		}
202 		errstr(err, sizeof err);
203 		if(strstr(err, "alarm") == 0){
204 			werrstr(err);
205 			break;
206 		}
207 		werrstr(err);
208 	}
209 	alarm(0);
210 	return rv;
211 }
212 
213 #define MSG "traceroute probe"
214 #define MAGIC 0xdead
215 
216 static int
217 icmpprobe(int cfd, int dfd, char *dest, int interval)
218 {
219 	int x, i, n, len, rv;
220 	char buf[512];
221 	Icmp *ip;
222 	char msg[Maxstring];
223 	char err[Maxstring];
224 
225 	seek(cfd, 0, 0);
226 	n = snprint(msg, sizeof msg, "connect %s", dest);
227 	if(write(cfd, msg, n)< 0)
228 		return -1;
229 
230 	rv = -1;
231 	ip = (Icmp*)buf;
232 	for(i = 0; i < 3; i++){
233 		alarm(interval/3);
234 		ip->type = EchoRequest;
235 		ip->code = 0;
236 		strcpy((char*)ip->data, MSG);
237 		ip->seq[0] = MAGIC;
238 		ip->seq[1] = MAGIC>>8;
239 		len = ICMP_IPSIZE+ICMP_HDRSIZE+sizeof(MSG);
240 
241 		/* send a request */
242 		if(write(dfd, buf, len) < len)
243 			break;
244 
245 		/* wait for reply */
246 		n = read(dfd, buf, sizeof(buf));
247 		alarm(0);
248 		if(n < 0){
249 			errstr(err, sizeof err);
250 			if(strstr(err, "alarm") == 0){
251 				werrstr(err);
252 				break;
253 			}
254 			werrstr(err);
255 			continue;
256 		}
257 		x = (ip->seq[1]<<8)|ip->seq[0];
258 		if(n >= len)
259 		if(ip->type == EchoReply)
260 		if(x == MAGIC)
261 		if(strcmp((char*)ip->data, MSG) == 0){
262 			rv = 0;
263 			break;
264 		}
265 	}
266 	alarm(0);
267 	return rv;
268 }
269 
270 static void
271 catch(void *a, char *msg)
272 {
273 	USED(a);
274 	if(strstr(msg, "alarm"))
275 		noted(NCONT);
276 	else
277 		noted(NDFLT);
278 }
279 
280 static int
281 call(DS *ds, char *clone, char *dest, int ttl, long *interval)
282 {
283 	int cfd, dfd, rv, n;
284 	char msg[Maxstring];
285 	char file[Maxstring];
286 	vlong start;
287 
288 	notify(catch);
289 
290 	/* start timing */
291 	start = nsec()/1000;
292 	rv = -1;
293 
294 	cfd = open(clone, ORDWR);
295 	if(cfd < 0){
296 		werrstr("%s: %r", clone);
297 		return -1;
298 	}
299 	dfd = -1;
300 
301 	/* get conversation number */
302 	n = read(cfd, msg, sizeof(msg)-1);
303 	if(n <= 0)
304 		goto out;
305 	msg[n] = 0;
306 
307 	/* open data file */
308 	sprint(file, "%s/%s/%s/data", ds->netdir, ds->proto, msg);
309 	dfd = open(file, ORDWR);
310 	if(dfd < 0)
311 		goto out;
312 
313 	/* set ttl */
314 	if(ttl)
315 		fprint(cfd, "ttl %d", ttl);
316 
317 	/* probe */
318 	if(strcmp(ds->proto, "udp") == 0)
319 		rv = udpprobe(cfd, dfd, dest, 3000);
320 	else if(strcmp(ds->proto, "icmp") == 0)
321 		rv = icmpprobe(cfd, dfd, dest, 3000);
322 	else	/* il and tcp */
323 		rv = tcpilprobe(cfd, dfd, dest, 3000);
324 out:
325 	/* turn off alarms */
326 	alarm(0);
327 	*interval = nsec()/1000 - start;
328 	close(cfd);
329 	close(dfd);
330 	return rv;
331 }
332 
333 /*
334  *  parse a dial string.  default netdir is /net.
335  *  default proto is tcp.
336  */
337 static void
338 dial_string_parse(char *str, DS *ds)
339 {
340 	char *p, *p2;
341 
342 	strncpy(ds->buf, str, Maxstring);
343 	ds->buf[Maxstring-3] = 0;
344 
345 	p = strchr(ds->buf, '!');
346 	if(p == 0) {
347 		ds->netdir = 0;
348 		ds->proto = "tcp";
349 		ds->rem = ds->buf;
350 	} else {
351 		if(*ds->buf != '/'){
352 			ds->netdir = 0;
353 			ds->proto = ds->buf;
354 		} else {
355 			for(p2 = p; *p2 != '/'; p2--)
356 				;
357 			*p2++ = 0;
358 			ds->netdir = ds->buf;
359 			ds->proto = p2;
360 		}
361 		*p = 0;
362 		ds->rem = p + 1;
363 	}
364 	if(strchr(ds->rem, '!') == 0)
365 		strcat(ds->rem, "!32767");
366 }
367 
368 void
369 main(int argc, char **argv)
370 {
371 	int j, done;
372 	DS ds;
373 	char clone[Maxpath], dest[Maxstring], hop[Maxstring], dom[Maxstring];
374 	char err[Maxstring];
375 	long lo, hi, sum, x;
376 	char *p;
377 	int tries, notranslate;
378 	char *net;
379 	int buckets, ttl;
380 	long *t;
381 
382 	buckets = 0;
383 	tries = 3;
384 	notranslate = 0;
385 	net = "/net";
386 	ttl = 1;
387 	ARGBEGIN{
388 	case 't':
389 		p = ARGF();
390 		if(p)
391 			ttl = atoi(p);
392 		break;
393 	case 'h':
394 		p = ARGF();
395 		if(p)
396 			buckets = atoi(p);
397 		break;
398 	case 'd':
399 		debug++;
400 		break;
401 	case 'n':
402 		notranslate++;
403 		break;
404 	case 'a':
405 		p = ARGF();
406 		if(p)
407 			tries = atoi(p);
408 		break;
409 	case 'x':
410 		net = ARGF();
411 		break;
412 	default:
413 		usage();
414 	}ARGEND;
415 
416 	if(argc < 1)
417 		usage();
418 
419 	t = malloc(tries*sizeof(ulong));
420 
421 	dial_string_parse(argv[0], &ds);
422 
423 	if(ds.netdir == 0)
424 		ds.netdir = net;
425 	if(csquery(&ds, clone, dest) < 0){
426 		fprint(2, "%s: %s: %r\n", argv0, argv[0]);
427 		exits(0);
428 	}
429 	print("trying %s/%s!%s\n\n", ds.netdir, ds.proto, dest);
430 	print("                       round trip times in µs\n");
431 	print("                        low      avg     high\n");
432 	print("                     --------------------------\n");
433 
434 	done = 0;
435 	for(; ttl < 32; ttl++){
436 		for(j = 0; j < tries; j++){
437 			if(call(&ds, clone, dest, ttl, &t[j]) >= 0){
438 				if(debug)
439 					print("%ld %s\n", t[j], dest);
440 				strcpy(hop, dest);
441 				done = 1;
442 				continue;
443 			}
444 			errstr(err, sizeof err);
445 			if(strstr(err, "refused")){
446 				strcpy(hop, dest);
447 				p = strchr(hop, '!');
448 				if(p)
449 					*p = 0;
450 				done = 1;
451 			} else if(strstr(err, "unreachable")){
452 				snprint(hop, sizeof(hop), "%s", err);
453 				p = strchr(hop, '!');
454 				if(p)
455 					*p = 0;
456 				done = 1;
457 			} else if(strncmp(err, "ttl exceeded at ", 16) == 0){
458 				strcpy(hop, err+16);
459 			} else {
460 				strcpy(hop, "*");
461 				break;
462 			}
463 			if(debug)
464 				print("%ld %s\n", t[j], hop);
465 		}
466 		if(strcmp(hop, "*") == 0){
467 			print("*\n");
468 			continue;
469 		}
470 		lo = 10000000;
471 		hi = 0;
472 		sum = 0;
473 		for(j = 0; j < tries; j++){
474 			x = t[j];
475 			sum += x;
476 			if(x < lo)
477 				lo = x;
478 			if(x > hi)
479 				hi = x;
480 		}
481 		if(notranslate == 1 || dodnsquery(&ds, hop, dom) < 0)
482 			dom[0] = 0;
483 		print("%-18.18s %8ld %8ld %8ld %s\n", hop, lo, sum/tries, hi, dom);
484 		if(buckets)
485 			histogram(t, tries, buckets, lo, hi);
486 		if(done)
487 			break;
488 	}
489 
490 	exits(0);
491 }
492 
493 char *order = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
494 
495 void
496 histogram(long *t, int n, int buckets, long lo, long hi)
497 {
498 	int i, j, empty;
499 	long span;
500 	static char *bar;
501 	char *p;
502 	char x[64];
503 
504 	if(bar == nil)
505 		bar = malloc(n+1);
506 
507 	print("+++++++++++++++++++++++\n");
508 	span = (hi-lo)/buckets;
509 	span++;
510 	empty = 0;
511 	for(i = 0; i < buckets; i++){
512 		p = bar;
513 		for(j = 0; j < n; j++)
514 			if(t[j] >= lo+i*span && t[j] <= lo+(i+1)*span)
515 				*p++ = order[j];
516 		*p = 0;
517 		if(p != bar){
518 			snprint(x, sizeof x, "[%ld-%ld]", lo+i*span, lo+(i+1)*span);
519 			print("%-16s %s\n", x, bar);
520 			empty = 0;
521 		} else if(!empty){
522 			print("...\n");
523 			empty = 1;
524 		}
525 	}
526 	print("+++++++++++++++++++++++\n");
527 }
528