Subversion Repositories planix.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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
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
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
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
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
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
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 *
139
geticmp(void *v)
140
{
141
	char *p = v;
142
 
143
	return (Icmphdr *)(p + proto->iphdrsz);
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
		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
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
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
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
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
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
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
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
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
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
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
}