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