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