]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/traceroute.c
ip/ipconfig: treat /32 mask as /0
[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 /* ICMPv4 only */
184 static int
185 icmpprobe(int cfd, int dfd, char *dest, int interval)
186 {
187         int x, i, n, len, rv;
188         char buf[512], err[ERRMAX], 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                         *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 == EchoReply && 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, dest, 3000);
287         else    /* il and tcp */
288                 rv = tcpilprobe(cfd, dfd, dest, 3000);
289 out:
290         /* turn off alarms */
291         alarm(0);
292         *interval = nsec()/1000 - start;
293         close(cfd);
294         close(dfd);
295         return rv;
296 }
297
298 /*
299  *  parse a dial string.  default netdir is /net.
300  *  default proto is tcp.
301  */
302 static void
303 dial_string_parse(char *str, DS *ds)
304 {
305         char *p, *p2;
306
307         strncpy(ds->buf, str, Maxstring);
308         ds->buf[Maxstring-3] = 0;
309
310         p = strchr(ds->buf, '!');
311         if(p == 0) {
312                 ds->netdir = 0;
313                 ds->proto = "tcp";
314                 ds->rem = ds->buf;
315         } else {
316                 if(*ds->buf != '/'){
317                         ds->netdir = 0;
318                         ds->proto = ds->buf;
319                 } else {
320                         for(p2 = p; *p2 != '/'; p2--)
321                                 ;
322                         *p2++ = 0;
323                         ds->netdir = ds->buf;
324                         ds->proto = p2;
325                 }
326                 *p = 0;
327                 ds->rem = p + 1;
328         }
329         if(strchr(ds->rem, '!') == 0)
330                 strcat(ds->rem, "!32767");
331 }
332
333 void
334 main(int argc, char **argv)
335 {
336         int buckets, ttl, j, done, tries, notranslate;
337         long lo, hi, sum, x;
338         long *t;
339         char *net, *p;
340         char clone[Maxpath], dest[Maxstring], hop[Maxstring], dom[Maxstring];
341         char err[ERRMAX];
342         DS ds;
343
344         buckets = 0;
345         tries = 3;
346         notranslate = 0;
347         net = "/net";
348         ttl = 1;
349         ARGBEGIN{
350         case 'a':
351                 tries = atoi(EARGF(usage()));
352                 break;
353         case 'd':
354                 debug++;
355                 break;
356         case 'h':
357                 buckets = atoi(EARGF(usage()));
358                 break;
359         case 'n':
360                 notranslate++;
361                 break;
362         case 't':
363                 ttl = atoi(EARGF(usage()));
364                 break;
365         case 'x':
366                 net = EARGF(usage());
367                 break;
368         default:
369                 usage();
370         }ARGEND;
371
372         if(argc < 1)
373                 usage();
374
375         t = malloc(tries*sizeof(ulong));
376
377         dial_string_parse(argv[0], &ds);
378
379         if(ds.netdir == 0)
380                 ds.netdir = net;
381         if(csquery(&ds, clone, dest) < 0){
382                 fprint(2, "%s: %s: %r\n", argv0, argv[0]);
383                 exits(0);
384         }
385         print("trying %s/%s!%s\n\n", ds.netdir, ds.proto, dest);
386         print("                       round trip times in µs\n");
387         print("                        low      avg     high\n");
388         print("                     --------------------------\n");
389
390         done = 0;
391         for(; ttl < 32; ttl++){
392                 for(j = 0; j < tries; j++){
393                         if(call(&ds, clone, dest, ttl, &t[j]) >= 0){
394                                 if(debug)
395                                         print("%ld %s\n", t[j], dest);
396                                 strcpy(hop, dest);
397                                 done = 1;
398                                 continue;
399                         }
400                         *err = 0;
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 }