xref: /plan9-contrib/sys/src/cmd/ip/ping.c (revision ec59a3ddbfceee0efe34584c2c9981a5e5ff1ec4)
1 #include <u.h>
2 #include <libc.h>
3 #include <ip.h>
4 
5 typedef struct Icmp Icmp;
6 struct Icmp
7 {
8 	uchar	vihl;		/* Version and header length */
9 	uchar	tos;		/* Type of service */
10 	uchar	length[2];	/* packet length */
11 	uchar	id[2];		/* Identification */
12 	uchar	frag[2];	/* Fragment information */
13 	uchar	ttl;		/* Time to live */
14 	uchar	proto;		/* Protocol */
15 	uchar	ipcksum[2];	/* Header checksum */
16 	uchar	src[4];		/* Ip source */
17 	uchar	dst[4];		/* Ip destination */
18 	uchar	type;
19 	uchar	code;
20 	uchar	cksum[2];
21 	uchar	icmpid[2];
22 	uchar	seq[2];
23 	uchar	data[1];
24 };
25 
26 enum
27 {			/* Packet Types */
28 	EchoReply	= 0,
29 	Unreachable	= 3,
30 	SrcQuench	= 4,
31 	EchoRequest	= 8,
32 	TimeExceed	= 11,
33 	Timestamp	= 13,
34 	TimestampReply	= 14,
35 	InfoRequest	= 15,
36 	InfoReply	= 16,
37 
38 	ICMP_IPSIZE	= 20,
39 	ICMP_HDRSIZE	= 8,
40 
41 	MAXMSG		= 32,
42 	SLEEPMS		= 1000,
43 };
44 
45 typedef struct Req Req;
46 struct Req
47 {
48 	ushort	seq;	// sequence number
49 	vlong	time;	// time sent
50 	vlong	rtt;
51 	int	ttl;
52 	int	replied;
53 	Req	 *next;
54 };
55 Req	*first;		// request list
56 Req	*last;		// ...
57 Lock	listlock;
58 
59 char *argv0;
60 int debug;
61 int quiet;
62 int lostonly;
63 int lostmsgs;
64 int rcvdmsgs;
65 int done;
66 int rint;
67 vlong sum;
68 ushort firstseq;
69 int addresses;
70 int flood;
71 
72 void usage(void);
73 void lost(Req*, Icmp*);
74 void reply(Req*, Icmp*);
75 
76 #define SECOND 1000000000LL
77 #define MINUTE (60LL*SECOND)
78 
79 static void
80 catch(void *a, char *msg)
81 {
82 	USED(a);
83 	if(strstr(msg, "alarm"))
84 		noted(NCONT);
85 	else if(strstr(msg, "die"))
86 		exits("errors");
87 	else
88 		noted(NDFLT);
89 }
90 
91 void
92 clean(ushort seq, vlong now, Icmp *ip)
93 {
94 	Req **l, *r;
95 
96 	lock(&listlock);
97 	last = nil;
98 	for(l = &first; *l; ){
99 		r = *l;
100 
101 		if(ip && r->seq == seq){
102 			r->rtt = now-r->time;
103 			r->ttl = ip->ttl;
104 			reply(r, ip);
105 		}
106 
107 		if(now-r->time > MINUTE){
108 			*l = r->next;
109 			r->rtt = now-r->time;
110 			if(ip)
111 				r->ttl = ip->ttl;
112 			if(r->replied == 0)
113 				lost(r, ip);
114 			free(r);
115 		}else{
116 			last = r;
117 			l = &(r->next);
118 		}
119 	}
120 	unlock(&listlock);
121 }
122 
123 void
124 sender(int fd, int msglen, int interval, int n)
125 {
126 	char buf[64*1024+512];
127 	Icmp *ip;
128 	int i, extra;
129 	Req *r;
130 	ushort seq;
131 
132 	ip = (Icmp*)buf;
133 
134 	srand(time(0));
135 	firstseq = seq = rand();
136 
137 	for(i = 32; i < msglen; i++)
138 		buf[i] = i;
139 	ip->type = EchoRequest;
140 	ip->code = 0;
141 
142 	for(i = 0; i < n; i++){
143 		if(i != 0){
144 			extra = rint? nrand(interval): 0;
145 			sleep(interval + extra);
146 		}
147 		r = malloc(sizeof *r);
148 		if(r != nil){
149 			hnputs(ip->seq, seq);
150 			r->seq = seq;
151 			r->next = nil;
152 			r->replied = 0;
153 			r->time = nsec();	/* avoid early free in reply! */
154 			lock(&listlock);
155 			if(first == nil)
156 				first = r;
157 			else
158 				last->next = r;
159 			last = r;
160 			unlock(&listlock);
161 			r->time = nsec();
162 			if(write(fd, ip, msglen) < msglen){
163 				fprint(2, "%s: write failed: %r\n", argv0);
164 				return;
165 			}
166 			seq++;
167 		}
168 	}
169 	done = 1;
170 }
171 
172 void
173 rcvr(int fd, int msglen, int interval, int nmsg)
174 {
175 	uchar buf[64*1024+512];
176 	Icmp *ip;
177 	ushort x;
178 	int i, n, munged;
179 	vlong now;
180 	Req *r;
181 
182 	ip = (Icmp*)buf;
183 	sum = 0;
184 
185 	while(lostmsgs+rcvdmsgs < nmsg){
186 		alarm((nmsg-lostmsgs-rcvdmsgs)*interval+5000);
187 		n = read(fd, buf, sizeof(buf));
188 		alarm(0);
189 		now = nsec();
190 		if(n <= 0){	/* read interrupted - time to go */
191 			clean(0, now+MINUTE, nil);
192 			continue;
193 		}
194 		if(n < msglen){
195 			print("bad len %d/%d\n", n, msglen);
196 			continue;
197 		}
198 		munged = 0;
199 		for(i = 32; i < msglen; i++)
200 			if(buf[i] != (i&0xff))
201 				munged++;
202 		if(munged)
203 			print("currupted reply\n");
204 		x = nhgets(ip->seq);
205 		if(ip->type != EchoReply || ip->code != 0) {
206 			print("bad sequence/code/type %d/%d/%d\n",
207 				ip->type, ip->code, x);
208 			continue;
209 		}
210 		clean(x, now, ip);
211 	}
212 
213 	lock(&listlock);
214 	for(r = first; r; r = r->next)
215 		if(r->replied == 0)
216 			lostmsgs++;
217 	unlock(&listlock);
218 
219 	if(lostmsgs)
220 		print("%d out of %d messages lost\n", lostmsgs, lostmsgs+rcvdmsgs);
221 }
222 
223 void
224 usage(void)
225 {
226 	fprint(2, "usage: %s [-alq] [-s msgsize] [-i millisecs] [-n #pings] destination\n", argv0);
227 	exits("usage");
228 }
229 
230 void
231 main(int argc, char **argv)
232 {
233 	int fd;
234 	int msglen, interval, nmsg;
235 
236 	nsec();		/* make sure time file is already open */
237 
238 	fmtinstall('V', eipfmt);
239 
240 	msglen = interval = 0;
241 	nmsg = MAXMSG;
242 	ARGBEGIN {
243 	case 'l':
244 		lostonly++;
245 		break;
246 	case 'd':
247 		debug++;
248 		break;
249 	case 's':
250 		msglen = atoi(ARGF());
251 		break;
252 	case 'i':
253 		interval = atoi(ARGF());
254 		break;
255 	case 'n':
256 		nmsg = atoi(ARGF());
257 		break;
258 	case 'a':
259 		addresses = 1;
260 		break;
261 	case 'q':
262 		quiet = 1;
263 		break;
264 	case 'r':
265 		rint = 1;
266 		break;
267 	case 'f':
268 		flood = 1;
269 		break;
270 	} ARGEND;
271 	if(msglen < 32)
272 		msglen = 64;
273 	if(msglen >= 65*1024)
274 		msglen = 65*1024-1;
275 	if(interval <= 0 && !flood)
276 		interval = SLEEPMS;
277 
278 	if(argc < 1)
279 		usage();
280 
281 	notify(catch);
282 
283 	fd = dial(netmkaddr(argv[0], "icmp", "1"), 0, 0, 0);
284 	if(fd < 0){
285 		fprint(2, "%s: couldn't dial: %r\n", argv0);
286 		exits("dialing");
287 	}
288 
289 	print("sending %d %d byte messages %d ms apart\n", nmsg, msglen, interval);
290 
291 	switch(rfork(RFPROC|RFMEM|RFFDG)){
292 	case -1:
293 		fprint(2, "%s: can't fork: %r\n", argv0);
294 	case 0:
295 		rcvr(fd, msglen, interval, nmsg);
296 		exits(0);
297 	default:
298 		sender(fd, msglen, interval, nmsg);
299 		wait();
300 		exits(lostmsgs ? "lost messages" : "");
301 	}
302 }
303 
304 void
305 reply(Req *r, Icmp *ip)
306 {
307 	r->rtt /= 1000LL;
308 	sum += r->rtt;
309 	if(!r->replied)
310 		rcvdmsgs++;
311 	if(!quiet && !lostonly){
312 		if(addresses)
313 			print("%ud: %V->%V rtt %lld µs, avg rtt %lld µs, ttl = %d\n",
314 				r->seq-firstseq,
315 				ip->src, ip->dst,
316 				r->rtt, sum/rcvdmsgs, r->ttl);
317 		else
318 			print("%ud: rtt %lld µs, avg rtt %lld µs, ttl = %d\n",
319 				r->seq-firstseq,
320 				r->rtt, sum/rcvdmsgs, r->ttl);
321 	}
322 	r->replied = 1;
323 }
324 
325 void
326 lost(Req *r, Icmp *ip)
327 {
328 	if(!quiet){
329 		if(addresses)
330 			print("lost %ud: %V->%V\n", r->seq-firstseq,
331 				ip->src, ip->dst);
332 		else
333 			print("lost %ud\n", r->seq-firstseq);
334 	}
335 	lostmsgs++;
336 }
337 
338