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