]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/traceroute.c
merge
[plan9front.git] / sys / src / cmd / ip / traceroute.c
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
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
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
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
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
140 udpprobe(int cfd, int dfd, char *dest, int interval)
141 {
142         int n, i, rv;
143         char msg[Maxstring], err[ERRMAX];
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                 *err = 0;
169                 errstr(err, sizeof err);
170                 if(strcmp(err, "interrupted") != 0){
171                         errstr(err, sizeof err);
172                         break;
173                 }
174                 errstr(err, sizeof err);
175         }
176         alarm(0);
177         return rv;
178 }
179
180 #define MSG "traceroute probe"
181 #define MAGIC 0xdead
182
183 static int
184 icmpprobe(int cfd, int dfd, int version, char *dest, int interval)
185 {
186         int x, i, n, len, rv;
187         char buf[512], err[ERRMAX], msg[Maxstring];
188         Icmphdr *ip;
189
190         seek(cfd, 0, 0);
191         n = snprint(msg, sizeof msg, "connect %s", dest);
192         if(write(cfd, msg, n)< 0)
193                 return -1;
194
195         rv = -1;
196         len = (version == 4)? IPV4HDR_LEN: IPV6HDR_LEN;
197         ip = (Icmphdr *)(buf + len);
198         len += ICMP_HDRSIZE + sizeof(MSG);
199         for(i = 0; i < 3; i++){
200                 alarm(interval/3);
201                 ip->type = (version == 4)? EchoRequest: EchoRequestV6;
202                 ip->code = 0;
203                 strcpy((char*)ip->data, MSG);
204                 ip->seq[0] = MAGIC;
205                 ip->seq[1] = MAGIC>>8;
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                         *err = 0;
216                         errstr(err, sizeof err);
217                         if(strcmp(err, "interrupted") != 0){
218                                 errstr(err, sizeof err);
219                                 break;
220                         }
221                         errstr(err, sizeof err);
222                         continue;
223                 }
224                 x = (ip->seq[1]<<8) | ip->seq[0];
225                 if(n >= len && ip->type == ((version == 4)? EchoReply: EchoReplyV6) && x == MAGIC &&
226                     strcmp((char*)ip->data, MSG) == 0){
227                         rv = 0;
228                         break;
229                 }
230         }
231         alarm(0);
232         return rv;
233 }
234
235 static void
236 catch(void *a, char *msg)
237 {
238         USED(a);
239         if(strstr(msg, "alarm"))
240                 noted(NCONT);
241         else
242                 noted(NDFLT);
243 }
244
245 static int
246 call(DS *ds, char *clone, char *dest, int ttl, long *interval)
247 {
248         int cfd, dfd, rv, n;
249         char msg[Maxstring];
250         char file[Maxstring];
251         vlong start;
252
253         notify(catch);
254
255         /* start timing */
256         start = nsec()/1000;
257         rv = -1;
258
259         cfd = open(clone, ORDWR);
260         if(cfd < 0){
261                 werrstr("%s: %r", clone);
262                 return -1;
263         }
264         dfd = -1;
265
266         /* get conversation number */
267         n = read(cfd, msg, sizeof(msg)-1);
268         if(n <= 0)
269                 goto out;
270         msg[n] = 0;
271
272         /* open data file */
273         sprint(file, "%s/%s/%s/data", ds->netdir, ds->proto, msg);
274         dfd = open(file, ORDWR);
275         if(dfd < 0)
276                 goto out;
277
278         /* set ttl */
279         if(ttl)
280                 fprint(cfd, "ttl %d", ttl);
281
282         /* probe */
283         if(strcmp(ds->proto, "udp") == 0)
284                 rv = udpprobe(cfd, dfd, dest, 3000);
285         else if(strcmp(ds->proto, "icmp") == 0)
286                 rv = icmpprobe(cfd, dfd, 4, dest, 3000);
287         else if(strcmp(ds->proto, "icmpv6") == 0)
288                 rv = icmpprobe(cfd, dfd, 6, dest, 3000);
289         else    /* il and tcp */
290                 rv = tcpilprobe(cfd, dfd, dest, 3000);
291 out:
292         /* turn off alarms */
293         alarm(0);
294         *interval = nsec()/1000 - start;
295         close(cfd);
296         close(dfd);
297         return rv;
298 }
299
300 /*
301  *  parse a dial string.  default netdir is /net.
302  *  default proto is tcp.
303  */
304 static void
305 dial_string_parse(char *str, DS *ds)
306 {
307         char *p, *p2;
308
309         strncpy(ds->buf, str, Maxstring);
310         ds->buf[Maxstring-3] = 0;
311
312         p = strchr(ds->buf, '!');
313         if(p == 0) {
314                 ds->netdir = 0;
315                 ds->proto = "tcp";
316                 ds->rem = ds->buf;
317         } else {
318                 if(*ds->buf != '/'){
319                         ds->netdir = 0;
320                         ds->proto = ds->buf;
321                 } else {
322                         for(p2 = p; *p2 != '/'; p2--)
323                                 ;
324                         *p2++ = 0;
325                         ds->netdir = ds->buf;
326                         ds->proto = p2;
327                 }
328                 *p = 0;
329                 ds->rem = p + 1;
330         }
331         if(strchr(ds->rem, '!') == 0)
332                 strcat(ds->rem, "!32767");
333 }
334
335 void
336 main(int argc, char **argv)
337 {
338         int buckets, ttl, j, done, tries, notranslate;
339         long lo, hi, sum, x;
340         long *t;
341         char *net, *p;
342         char clone[Maxpath], dest[Maxstring], hop[Maxstring], dom[Maxstring];
343         char err[ERRMAX];
344         DS ds;
345
346         buckets = 0;
347         tries = 3;
348         notranslate = 0;
349         net = "/net";
350         ttl = 1;
351         ARGBEGIN{
352         case 'a':
353                 tries = atoi(EARGF(usage()));
354                 break;
355         case 'd':
356                 debug++;
357                 break;
358         case 'h':
359                 buckets = atoi(EARGF(usage()));
360                 break;
361         case 'n':
362                 notranslate++;
363                 break;
364         case 't':
365                 ttl = atoi(EARGF(usage()));
366                 break;
367         case 'x':
368                 net = EARGF(usage());
369                 break;
370         default:
371                 usage();
372         }ARGEND;
373
374         if(argc < 1)
375                 usage();
376
377         t = malloc(tries*sizeof(ulong));
378
379         dial_string_parse(argv[0], &ds);
380
381         if(ds.netdir == 0)
382                 ds.netdir = net;
383         if(csquery(&ds, clone, dest) < 0){
384                 fprint(2, "%s: %s: %r\n", argv0, argv[0]);
385                 exits(0);
386         }
387         print("trying %s/%s!%s\n\n", ds.netdir, ds.proto, dest);
388         print("                       round trip times in µs\n");
389         print("                        low      avg     high\n");
390         print("                     --------------------------\n");
391
392         done = 0;
393         for(; ttl < 32; ttl++){
394                 for(j = 0; j < tries; j++){
395                         if(call(&ds, clone, dest, ttl, &t[j]) >= 0){
396                                 if(debug)
397                                         print("%ld %s\n", t[j], dest);
398                                 strcpy(hop, dest);
399                                 done = 1;
400                                 continue;
401                         }
402                         *err = 0;
403                         errstr(err, sizeof err);
404                         if(strstr(err, "refused")){
405                                 strcpy(hop, dest);
406                                 p = strchr(hop, '!');
407                                 if(p)
408                                         *p = 0;
409                                 done = 1;
410                         } else if(strstr(err, "unreachable")){
411                                 snprint(hop, sizeof(hop), "%s", err);
412                                 p = strchr(hop, '!');
413                                 if(p)
414                                         *p = 0;
415                                 done = 1;
416                         } else if(strncmp(err, "ttl exceeded at ", 16) == 0)
417                                 strcpy(hop, err+16);
418                         else {
419                                 strcpy(hop, "*");
420                                 break;
421                         }
422                         if(debug)
423                                 print("%ld %s\n", t[j], hop);
424                 }
425                 if(strcmp(hop, "*") == 0){
426                         print("*\n");
427                         continue;
428                 }
429                 lo = 10000000;
430                 hi = 0;
431                 sum = 0;
432                 for(j = 0; j < tries; j++){
433                         x = t[j];
434                         sum += x;
435                         if(x < lo)
436                                 lo = x;
437                         if(x > hi)
438                                 hi = x;
439                 }
440                 if(notranslate == 1 || dodnsquery(&ds, hop, dom) < 0)
441                         dom[0] = 0;
442                 /* don't truncate: ipv6 addresses can be quite long */
443                 print("%-18s %8ld %8ld %8ld %s\n", hop, lo, sum/tries, hi, dom);
444                 if(buckets)
445                         histogram(t, tries, buckets, lo, hi);
446                 if(done)
447                         break;
448         }
449         exits(0);
450 }
451
452 char *order = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
453
454 void
455 histogram(long *t, int n, int buckets, long lo, long hi)
456 {
457         int i, j, empty;
458         long span;
459         static char *bar;
460         char *p;
461         char x[64];
462
463         if(bar == nil)
464                 bar = malloc(n+1);
465
466         print("+++++++++++++++++++++++\n");
467         span = (hi-lo)/buckets;
468         span++;
469         empty = 0;
470         for(i = 0; i < buckets; i++){
471                 p = bar;
472                 for(j = 0; j < n; j++)
473                         if(t[j] >= lo+i*span && t[j] <= lo+(i+1)*span)
474                                 *p++ = order[j];
475                 *p = 0;
476                 if(p != bar){
477                         snprint(x, sizeof x, "[%ld-%ld]", lo+i*span, lo+(i+1)*span);
478                         print("%-16s %s\n", x, bar);
479                         empty = 0;
480                 } else if(!empty){
481                         print("...\n");
482                         empty = 1;
483                 }
484         }
485         print("+++++++++++++++++++++++\n");
486 }