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