xref: /plan9/sys/src/cmd/ip/ping.c (revision f9e1cf08d3be51592e03e639fc848a68dc31a55e)
1 /* ping for ip v4 and v6 */
2 #include <u.h>
3 #include <libc.h>
4 #include <ctype.h>
5 #include <ip.h>
6 #include <bio.h>
7 #include <ndb.h>
8 #include "icmp.h"
9 
10 enum {
11 	MAXMSG		= 32,
12 	SLEEPMS		= 1000,
13 
14 	SECOND		= 1000000000LL,
15 	MINUTE		= 60*SECOND,
16 };
17 
18 typedef struct Req Req;
19 struct Req
20 {
21 	ushort	seq;	/* sequence number */
22 	vlong	time;	/* time sent */
23 	vlong	rtt;
24 	int	ttl;
25 	int	replied;
26 	Req	 *next;
27 };
28 
29 typedef struct {
30 	int	version;
31 	char	*net;
32 	int	echocmd;
33 	int	echoreply;
34 	unsigned iphdrsz;
35 
36 	void	(*prreply)(Req *r, void *v);
37 	void	(*prlost)(ushort seq, void *v);
38 } Proto;
39 
40 
41 Req	*first;		/* request list */
42 Req	*last;		/* ... */
43 Lock	listlock;
44 
45 char *argv0;
46 
47 int addresses;
48 int debug;
49 int done;
50 int flood;
51 int lostmsgs;
52 int lostonly;
53 int quiet;
54 int rcvdmsgs;
55 int rint;
56 ushort firstseq;
57 vlong sum;
58 
59 static char *network, *target;
60 
61 void lost(Req*, void*);
62 void reply(Req*, void*);
63 
64 static void
65 usage(void)
66 {
67 	fprint(2,
68 	    "usage: %s [-6alq] [-s msgsize] [-i millisecs] [-n #pings] dest\n",
69 		argv0);
70 	exits("usage");
71 }
72 
73 static void
74 catch(void *a, char *msg)
75 {
76 	USED(a);
77 	if(strstr(msg, "alarm"))
78 		noted(NCONT);
79 	else if(strstr(msg, "die"))
80 		exits("errors");
81 	else
82 		noted(NDFLT);
83 }
84 
85 static void
86 prlost4(ushort seq, void *v)
87 {
88 	Icmp *ip4 = v;
89 
90 	print("lost %ud: %V -> %V\n", seq, ip4->src, ip4->dst);
91 }
92 
93 static void
94 prlost6(ushort seq, void *v)
95 {
96 	Icmp6 *ip6 = v;
97 
98 	print("lost %ud: %I -> %I\n", seq, ip6->src, ip6->dst);
99 }
100 
101 static void
102 prreply4(Req *r, void *v)
103 {
104 	Icmp *ip4 = v;
105 
106 	print("%ud: %V -> %V rtt %lld µs, avg rtt %lld µs, ttl = %d\n",
107 		r->seq - firstseq, ip4->src, ip4->dst, r->rtt, sum/rcvdmsgs,
108 		r->ttl);
109 }
110 
111 static void
112 prreply6(Req *r, void *v)
113 {
114 	Icmp6 *ip6 = v;
115 
116 	print("%ud: %I -> %I rtt %lld µs, avg rtt %lld µs, ttl = %d\n",
117 		r->seq - firstseq, ip6->src, ip6->dst, r->rtt, sum/rcvdmsgs,
118 		r->ttl);
119 }
120 
121 static Proto v4pr = {
122 	4,		"icmp",
123 	EchoRequest,	EchoReply,
124 	IPV4HDR_LEN,
125 	prreply4,	prlost4,
126 };
127 static Proto v6pr = {
128 	6,		"icmpv6",
129 	EchoRequestV6,	EchoReplyV6,
130 	IPV6HDR_LEN,
131 	prreply6,	prlost6,
132 };
133 
134 static Proto *proto = &v4pr;
135 
136 
137 Icmphdr *
138 geticmp(void *v)
139 {
140 	char *p = v;
141 
142 	p += proto->iphdrsz;
143 	return (Icmphdr *)p;
144 }
145 
146 void
147 clean(ushort seq, vlong now, void *v)
148 {
149 	int ttl;
150 	Req **l, *r;
151 
152 	ttl = 0;
153 	if (v)
154 		ttl = proto->version == 4? ((Icmp *)v)->ttl: ((Icmp6 *)v)->ttl;
155 	lock(&listlock);
156 	last = nil;
157 	for(l = &first; *l; ){
158 		r = *l;
159 
160 		if(v && r->seq == seq){
161 			r->rtt = now-r->time;
162 			r->ttl = ttl;
163 			reply(r, v);
164 		}
165 
166 		if(now-r->time > MINUTE){
167 			*l = r->next;
168 			r->rtt = now-r->time;
169 			if(v)
170 				r->ttl = ttl;
171 			if(r->replied == 0)
172 				lost(r, v);
173 			free(r);
174 		}else{
175 			last = r;
176 			l = &r->next;
177 		}
178 	}
179 	unlock(&listlock);
180 }
181 
182 static uchar loopbacknet[IPaddrlen] = {
183 	0, 0, 0, 0,
184 	0, 0, 0, 0,
185 	0, 0, 0xff, 0xff,
186 	127, 0, 0, 0
187 };
188 static uchar loopbackmask[IPaddrlen] = {
189 	0xff, 0xff, 0xff, 0xff,
190 	0xff, 0xff, 0xff, 0xff,
191 	0xff, 0xff, 0xff, 0xff,
192 	0xff, 0, 0, 0
193 };
194 
195 /*
196  * find first ip addr suitable for proto and
197  * that isn't the friggin loopback address.
198  * deprecate link-local and multicast addresses.
199  */
200 static int
201 myipvnaddr(uchar *ip, Proto *proto, char *net)
202 {
203 	int ipisv4, wantv4;
204 	Ipifc *nifc;
205 	Iplifc *lifc;
206 	uchar mynet[IPaddrlen], linklocal[IPaddrlen];
207 	static Ipifc *ifc;
208 
209 	ipmove(linklocal, IPnoaddr);
210 	wantv4 = proto->version == 4;
211 	ifc = readipifc(net, ifc, -1);
212 	for(nifc = ifc; nifc; nifc = nifc->next)
213 		for(lifc = nifc->lifc; lifc; lifc = lifc->next){
214 			maskip(lifc->ip, loopbackmask, mynet);
215 			if(ipcmp(mynet, loopbacknet) == 0)
216 				continue;
217 			if(ISIPV6MCAST(lifc->ip) || ISIPV6LINKLOCAL(lifc->ip)) {
218 				ipmove(linklocal, lifc->ip);
219 				continue;
220 			}
221 			ipisv4 = isv4(lifc->ip) != 0;
222 			if(ipcmp(lifc->ip, IPnoaddr) != 0 && wantv4 == ipisv4){
223 				ipmove(ip, lifc->ip);
224 				return 0;
225 			}
226 		}
227 	/* no global unicast addrs found, fall back to link-local, if any */
228 	ipmove(ip, linklocal);
229 	return ipcmp(ip, IPnoaddr) == 0? -1: 0;
230 }
231 
232 void
233 sender(int fd, int msglen, int interval, int n)
234 {
235 	int i, extra;
236 	ushort seq;
237 	char buf[64*1024+512];
238 	uchar me[IPaddrlen], mev4[IPv4addrlen];
239 	Icmphdr *icmp;
240 	Req *r;
241 
242 	srand(time(0));
243 	firstseq = seq = rand();
244 
245 	icmp = geticmp(buf);
246 	memset(buf, 0, proto->iphdrsz + ICMP_HDRSIZE);
247 	for(i = proto->iphdrsz + ICMP_HDRSIZE; i < msglen; i++)
248 		buf[i] = i;
249 	icmp->type = proto->echocmd;
250 	icmp->code = 0;
251 
252 	/* arguably the kernel should fill in the right src addr. */
253 	myipvnaddr(me, proto, network);
254 	if (proto->version == 4) {
255 		v6tov4(mev4, me);
256 		memmove(((Icmp *)buf)->src, mev4, IPv4addrlen);
257 	} else
258 		ipmove(((Ip6hdr *)buf)->src, me);
259 	if (addresses)
260 		print("\t%I -> %s\n", me, target);
261 
262 	for(i = 0; i < n; i++){
263 		if(i != 0){
264 			extra = rint? nrand(interval): 0;
265 			sleep(interval + extra);
266 		}
267 		r = malloc(sizeof *r);
268 		if (r == nil)
269 			continue;
270 		hnputs(icmp->seq, seq);
271 		r->seq = seq;
272 		r->next = nil;
273 		r->replied = 0;
274 		r->time = nsec();	/* avoid early free in reply! */
275 		lock(&listlock);
276 		if(first == nil)
277 			first = r;
278 		else
279 			last->next = r;
280 		last = r;
281 		unlock(&listlock);
282 		r->time = nsec();
283 		if(write(fd, buf, msglen) < msglen){
284 			fprint(2, "%s: write failed: %r\n", argv0);
285 			return;
286 		}
287 		seq++;
288 	}
289 	done = 1;
290 }
291 
292 void
293 rcvr(int fd, int msglen, int interval, int nmsg)
294 {
295 	int i, n, munged;
296 	ushort x;
297 	vlong now;
298 	uchar buf[64*1024+512];
299 	Icmphdr *icmp;
300 	Req *r;
301 
302 	sum = 0;
303 	while(lostmsgs+rcvdmsgs < nmsg){
304 		alarm((nmsg-lostmsgs-rcvdmsgs)*interval+5000);
305 		n = read(fd, buf, sizeof buf);
306 		alarm(0);
307 		now = nsec();
308 		if(n <= 0){	/* read interrupted - time to go */
309 			clean(0, now+MINUTE, nil);
310 			continue;
311 		}
312 		if(n < msglen){
313 			print("bad len %d/%d\n", n, msglen);
314 			continue;
315 		}
316 		icmp = geticmp(buf);
317 		munged = 0;
318 		for(i = proto->iphdrsz + ICMP_HDRSIZE; i < msglen; i++)
319 			if(buf[i] != (uchar)i)
320 				munged++;
321 		if(munged)
322 			print("corrupted reply\n");
323 		x = nhgets(icmp->seq);
324 		if(icmp->type != proto->echoreply || icmp->code != 0) {
325 			print("bad type/code/sequence %d/%d/%d (want %d/%d/%d)\n",
326 				icmp->type, icmp->code, x,
327 				proto->echoreply, 0, x);
328 			continue;
329 		}
330 		clean(x, now, buf);
331 	}
332 
333 	lock(&listlock);
334 	for(r = first; r; r = r->next)
335 		if(r->replied == 0)
336 			lostmsgs++;
337 	unlock(&listlock);
338 
339 	if(!quiet && lostmsgs)
340 		print("%d out of %d messages lost\n", lostmsgs,
341 			lostmsgs+rcvdmsgs);
342 }
343 
344 static int
345 isdottedquad(char *name)
346 {
347 	int dot = 0, digit = 0;
348 
349 	for (; *name != '\0'; name++)
350 		if (*name == '.')
351 			dot++;
352 		else if (isdigit(*name))
353 			digit++;
354 		else
355 			return 0;
356 	return dot && digit;
357 }
358 
359 static int
360 isv6lit(char *name)
361 {
362 	int colon = 0, hex = 0;
363 
364 	for (; *name != '\0'; name++)
365 		if (*name == ':')
366 			colon++;
367 		else if (isxdigit(*name))
368 			hex++;
369 		else
370 			return 0;
371 	return colon;
372 }
373 
374 /* from /sys/src/libc/9sys/dial.c */
375 
376 enum
377 {
378 	Maxstring	= 128,
379 	Maxpath		= 256,
380 };
381 
382 typedef struct DS DS;
383 struct DS {
384 	/* dist string */
385 	char	buf[Maxstring];
386 	char	*netdir;
387 	char	*proto;
388 	char	*rem;
389 
390 	/* other args */
391 	char	*local;
392 	char	*dir;
393 	int	*cfdp;
394 };
395 
396 /*
397  *  parse a dial string
398  */
399 static void
400 _dial_string_parse(char *str, DS *ds)
401 {
402 	char *p, *p2;
403 
404 	strncpy(ds->buf, str, Maxstring);
405 	ds->buf[Maxstring-1] = 0;
406 
407 	p = strchr(ds->buf, '!');
408 	if(p == 0) {
409 		ds->netdir = 0;
410 		ds->proto = "net";
411 		ds->rem = ds->buf;
412 	} else {
413 		if(*ds->buf != '/' && *ds->buf != '#'){
414 			ds->netdir = 0;
415 			ds->proto = ds->buf;
416 		} else {
417 			for(p2 = p; *p2 != '/'; p2--)
418 				;
419 			*p2++ = 0;
420 			ds->netdir = ds->buf;
421 			ds->proto = p2;
422 		}
423 		*p = 0;
424 		ds->rem = p + 1;
425 	}
426 }
427 
428 /* end excerpt from /sys/src/libc/9sys/dial.c */
429 
430 /* side effect: sets network & target */
431 static int
432 isv4name(char *name)
433 {
434 	int r = 1;
435 	char *root, *ip, *pr;
436 	DS ds;
437 
438 	_dial_string_parse(name, &ds);
439 
440 	/* cope with leading /net.alt/icmp! and the like */
441 	root = nil;
442 	if (ds.netdir != nil) {
443 		pr = strrchr(ds.netdir, '/');
444 		if (pr == nil)
445 			pr = ds.netdir;
446 		else {
447 			*pr++ = '\0';
448 			root = ds.netdir;
449 			network = strdup(root);
450 		}
451 		if (strcmp(pr, v4pr.net) == 0)
452 			return 1;
453 		if (strcmp(pr, v6pr.net) == 0)
454 			return 0;
455 	}
456 
457 	/* if it's a literal, it's obvious from syntax which proto it is */
458 	free(target);
459 	target = strdup(ds.rem);
460 	if (isdottedquad(ds.rem))
461 		return 1;
462 	else if (isv6lit(ds.rem))
463 		return 0;
464 
465 	/* map name to ip and look at its syntax */
466 	ip = csgetvalue(root, "sys", ds.rem, "ip", nil);
467 	if (ip == nil)
468 		ip = csgetvalue(root, "dom", ds.rem, "ip", nil);
469 	if (ip == nil)
470 		ip = csgetvalue(root, "sys", ds.rem, "ipv6", nil);
471 	if (ip == nil)
472 		ip = csgetvalue(root, "dom", ds.rem, "ipv6", nil);
473 	if (ip != nil)
474 		r = isv4name(ip);
475 	free(ip);
476 	return r;
477 }
478 
479 void
480 main(int argc, char **argv)
481 {
482 	int fd, msglen, interval, nmsg;
483 	char *ds;
484 
485 	nsec();		/* make sure time file is already open */
486 
487 	fmtinstall('V', eipfmt);
488 	fmtinstall('I', eipfmt);
489 
490 	msglen = interval = 0;
491 	nmsg = MAXMSG;
492 	ARGBEGIN {
493 	case '6':
494 		proto = &v6pr;
495 		break;
496 	case 'a':
497 		addresses = 1;
498 		break;
499 	case 'd':
500 		debug++;
501 		break;
502 	case 'f':
503 		flood = 1;
504 		break;
505 	case 'i':
506 		interval = atoi(EARGF(usage()));
507 		break;
508 	case 'l':
509 		lostonly++;
510 		break;
511 	case 'n':
512 		nmsg = atoi(EARGF(usage()));
513 		break;
514 	case 'q':
515 		quiet = 1;
516 		break;
517 	case 'r':
518 		rint = 1;
519 		break;
520 	case 's':
521 		msglen = atoi(EARGF(usage()));
522 		break;
523 	default:
524 		usage();
525 		break;
526 	} ARGEND;
527 
528 	if(msglen < proto->iphdrsz + ICMP_HDRSIZE)
529 		msglen = proto->iphdrsz + ICMP_HDRSIZE;
530 	if(msglen < 64)
531 		msglen = 64;
532 	if(msglen >= 64*1024)
533 		msglen = 64*1024-1;
534 	if(interval <= 0 && !flood)
535 		interval = SLEEPMS;
536 
537 	if(argc < 1)
538 		usage();
539 
540 	notify(catch);
541 
542 	if (!isv4name(argv[0]))
543 		proto = &v6pr;
544 	ds = netmkaddr(argv[0], proto->net, "1");
545 	fd = dial(ds, 0, 0, 0);
546 	if(fd < 0){
547 		fprint(2, "%s: couldn't dial %s: %r\n", argv0, ds);
548 		exits("dialing");
549 	}
550 
551 	if (!quiet)
552 		print("sending %d %d byte messages %d ms apart to %s\n",
553 			nmsg, msglen, interval, ds);
554 
555 	switch(rfork(RFPROC|RFMEM|RFFDG)){
556 	case -1:
557 		fprint(2, "%s: can't fork: %r\n", argv0);
558 		/* fallthrough */
559 	case 0:
560 		rcvr(fd, msglen, interval, nmsg);
561 		exits(0);
562 	default:
563 		sender(fd, msglen, interval, nmsg);
564 		wait();
565 		exits(lostmsgs ? "lost messages" : "");
566 	}
567 }
568 
569 void
570 reply(Req *r, void *v)
571 {
572 	r->rtt /= 1000LL;
573 	sum += r->rtt;
574 	if(!r->replied)
575 		rcvdmsgs++;
576 	if(!quiet && !lostonly)
577 		if(addresses)
578 			(*proto->prreply)(r, v);
579 		else
580 			print("%ud: rtt %lld µs, avg rtt %lld µs, ttl = %d\n",
581 				r->seq - firstseq, r->rtt, sum/rcvdmsgs, r->ttl);
582 	r->replied = 1;
583 }
584 
585 void
586 lost(Req *r, void *v)
587 {
588 	if(!quiet)
589 		if(addresses && v != nil)
590 			(*proto->prlost)(r->seq - firstseq, v);
591 		else
592 			print("lost %ud\n", r->seq - firstseq);
593 	lostmsgs++;
594 }
595