xref: /plan9/sys/src/9/ip/icmp.c (revision 58da3067adcdccaaa043d0bfde28ba83b7ced07d)
1 #include "u.h"
2 #include "../port/lib.h"
3 #include "mem.h"
4 #include "dat.h"
5 #include "fns.h"
6 #include "../port/error.h"
7 
8 #include "ip.h"
9 
10 typedef struct Icmp {
11 	uchar	vihl;		/* Version and header length */
12 	uchar	tos;		/* Type of service */
13 	uchar	length[2];	/* packet length */
14 	uchar	id[2];		/* Identification */
15 	uchar	frag[2];	/* Fragment information */
16 	uchar	ttl;		/* Time to live */
17 	uchar	proto;		/* Protocol */
18 	uchar	ipcksum[2];	/* Header checksum */
19 	uchar	src[4];		/* Ip source */
20 	uchar	dst[4];		/* Ip destination */
21 	uchar	type;
22 	uchar	code;
23 	uchar	cksum[2];
24 	uchar	icmpid[2];
25 	uchar	seq[2];
26 	uchar	data[1];
27 } Icmp;
28 
29 enum {			/* Packet Types */
30 	EchoReply	= 0,
31 	Unreachable	= 3,
32 	SrcQuench	= 4,
33 	Redirect	= 5,
34 	EchoRequest	= 8,
35 	TimeExceed	= 11,
36 	InParmProblem	= 12,
37 	Timestamp	= 13,
38 	TimestampReply	= 14,
39 	InfoRequest	= 15,
40 	InfoReply	= 16,
41 	AddrMaskRequest = 17,
42 	AddrMaskReply   = 18,
43 
44 	Maxtype		= 18,
45 };
46 
47 enum
48 {
49 	MinAdvise	= 24,	/* minimum needed for us to advise another protocol */
50 };
51 
52 char *icmpnames[Maxtype+1] =
53 {
54 [EchoReply]		"EchoReply",
55 [Unreachable]		"Unreachable",
56 [SrcQuench]		"SrcQuench",
57 [Redirect]		"Redirect",
58 [EchoRequest]		"EchoRequest",
59 [TimeExceed]		"TimeExceed",
60 [InParmProblem]		"InParmProblem",
61 [Timestamp]		"Timestamp",
62 [TimestampReply]	"TimestampReply",
63 [InfoRequest]		"InfoRequest",
64 [InfoReply]		"InfoReply",
65 [AddrMaskRequest]	"AddrMaskRequest",
66 [AddrMaskReply  ]	"AddrMaskReply  ",
67 };
68 
69 enum {
70 	IP_ICMPPROTO	= 1,
71 	ICMP_IPSIZE	= 20,
72 	ICMP_HDRSIZE	= 8,
73 };
74 
75 enum
76 {
77 	InMsgs,
78 	InErrors,
79 	OutMsgs,
80 	CsumErrs,
81 	LenErrs,
82 	HlenErrs,
83 
84 	Nstats,
85 };
86 
87 static char *statnames[Nstats] =
88 {
89 [InMsgs]	"InMsgs",
90 [InErrors]	"InErrors",
91 [OutMsgs]	"OutMsgs",
92 [CsumErrs]	"CsumErrs",
93 [LenErrs]	"LenErrs",
94 [HlenErrs]	"HlenErrs",
95 };
96 
97 typedef struct Icmppriv Icmppriv;
98 struct Icmppriv
99 {
100 	ulong	stats[Nstats];
101 
102 	/* message counts */
103 	ulong	in[Maxtype+1];
104 	ulong	out[Maxtype+1];
105 };
106 
107 static void icmpkick(void *x, Block*);
108 
109 static void
icmpcreate(Conv * c)110 icmpcreate(Conv *c)
111 {
112 	c->rq = qopen(64*1024, Qmsg, 0, c);
113 	c->wq = qbypass(icmpkick, c);
114 }
115 
116 extern char*
icmpconnect(Conv * c,char ** argv,int argc)117 icmpconnect(Conv *c, char **argv, int argc)
118 {
119 	char *e;
120 
121 	e = Fsstdconnect(c, argv, argc);
122 	if(e != nil)
123 		return e;
124 	Fsconnected(c, e);
125 
126 	return nil;
127 }
128 
129 extern int
icmpstate(Conv * c,char * state,int n)130 icmpstate(Conv *c, char *state, int n)
131 {
132 	USED(c);
133 	return snprint(state, n, "%s qin %d qout %d\n",
134 		"Datagram",
135 		c->rq ? qlen(c->rq) : 0,
136 		c->wq ? qlen(c->wq) : 0
137 	);
138 }
139 
140 extern char*
icmpannounce(Conv * c,char ** argv,int argc)141 icmpannounce(Conv *c, char **argv, int argc)
142 {
143 	char *e;
144 
145 	e = Fsstdannounce(c, argv, argc);
146 	if(e != nil)
147 		return e;
148 	Fsconnected(c, nil);
149 
150 	return nil;
151 }
152 
153 extern void
icmpclose(Conv * c)154 icmpclose(Conv *c)
155 {
156 	qclose(c->rq);
157 	qclose(c->wq);
158 	ipmove(c->laddr, IPnoaddr);
159 	ipmove(c->raddr, IPnoaddr);
160 	c->lport = 0;
161 }
162 
163 static void
icmpkick(void * x,Block * bp)164 icmpkick(void *x, Block *bp)
165 {
166 	Conv *c = x;
167 	Icmp *p;
168 	Icmppriv *ipriv;
169 
170 	if(bp == nil)
171 		return;
172 
173 	if(blocklen(bp) < ICMP_IPSIZE + ICMP_HDRSIZE){
174 		freeblist(bp);
175 		return;
176 	}
177 	p = (Icmp *)(bp->rp);
178 	p->vihl = IP_VER4;
179 	ipriv = c->p->priv;
180 	if(p->type <= Maxtype)
181 		ipriv->out[p->type]++;
182 
183 	v6tov4(p->dst, c->raddr);
184 	v6tov4(p->src, c->laddr);
185 	p->proto = IP_ICMPPROTO;
186 	hnputs(p->icmpid, c->lport);
187 	memset(p->cksum, 0, sizeof(p->cksum));
188 	hnputs(p->cksum, ptclcsum(bp, ICMP_IPSIZE, blocklen(bp) - ICMP_IPSIZE));
189 	ipriv->stats[OutMsgs]++;
190 	ipoput4(c->p->f, bp, 0, c->ttl, c->tos, nil);
191 }
192 
193 extern void
icmpttlexceeded(Fs * f,uchar * ia,Block * bp)194 icmpttlexceeded(Fs *f, uchar *ia, Block *bp)
195 {
196 	Block	*nbp;
197 	Icmp	*p, *np;
198 
199 	p = (Icmp *)bp->rp;
200 
201 	netlog(f, Logicmp, "sending icmpttlexceeded -> %V\n", p->src);
202 	nbp = allocb(ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8);
203 	nbp->wp += ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8;
204 	np = (Icmp *)nbp->rp;
205 	np->vihl = IP_VER4;
206 	memmove(np->dst, p->src, sizeof(np->dst));
207 	v6tov4(np->src, ia);
208 	memmove(np->data, bp->rp, ICMP_IPSIZE + 8);
209 	np->type = TimeExceed;
210 	np->code = 0;
211 	np->proto = IP_ICMPPROTO;
212 	hnputs(np->icmpid, 0);
213 	hnputs(np->seq, 0);
214 	memset(np->cksum, 0, sizeof(np->cksum));
215 	hnputs(np->cksum, ptclcsum(nbp, ICMP_IPSIZE, blocklen(nbp) - ICMP_IPSIZE));
216 	ipoput4(f, nbp, 0, MAXTTL, DFLTTOS, nil);
217 
218 }
219 
220 static void
icmpunreachable(Fs * f,Block * bp,int code,int seq)221 icmpunreachable(Fs *f, Block *bp, int code, int seq)
222 {
223 	Block	*nbp;
224 	Icmp	*p, *np;
225 	int	i;
226 	uchar	addr[IPaddrlen];
227 
228 	p = (Icmp *)bp->rp;
229 
230 	/* only do this for unicast sources and destinations */
231 	v4tov6(addr, p->dst);
232 	i = ipforme(f, addr);
233 	if((i&Runi) == 0)
234 		return;
235 	v4tov6(addr, p->src);
236 	i = ipforme(f, addr);
237 	if(i != 0 && (i&Runi) == 0)
238 		return;
239 
240 	netlog(f, Logicmp, "sending icmpnoconv -> %V\n", p->src);
241 	nbp = allocb(ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8);
242 	nbp->wp += ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8;
243 	np = (Icmp *)nbp->rp;
244 	np->vihl = IP_VER4;
245 	memmove(np->dst, p->src, sizeof(np->dst));
246 	memmove(np->src, p->dst, sizeof(np->src));
247 	memmove(np->data, bp->rp, ICMP_IPSIZE + 8);
248 	np->type = Unreachable;
249 	np->code = code;
250 	np->proto = IP_ICMPPROTO;
251 	hnputs(np->icmpid, 0);
252 	hnputs(np->seq, seq);
253 	memset(np->cksum, 0, sizeof(np->cksum));
254 	hnputs(np->cksum, ptclcsum(nbp, ICMP_IPSIZE, blocklen(nbp) - ICMP_IPSIZE));
255 	ipoput4(f, nbp, 0, MAXTTL, DFLTTOS, nil);
256 }
257 
258 extern void
icmpnoconv(Fs * f,Block * bp)259 icmpnoconv(Fs *f, Block *bp)
260 {
261 	icmpunreachable(f, bp, 3, 0);
262 }
263 
264 extern void
icmpcantfrag(Fs * f,Block * bp,int mtu)265 icmpcantfrag(Fs *f, Block *bp, int mtu)
266 {
267 	icmpunreachable(f, bp, 4, mtu);
268 }
269 
270 static void
goticmpkt(Proto * icmp,Block * bp)271 goticmpkt(Proto *icmp, Block *bp)
272 {
273 	Conv	**c, *s;
274 	Icmp	*p;
275 	uchar	dst[IPaddrlen];
276 	ushort	recid;
277 
278 	p = (Icmp *) bp->rp;
279 	v4tov6(dst, p->src);
280 	recid = nhgets(p->icmpid);
281 
282 	for(c = icmp->conv; *c; c++) {
283 		s = *c;
284 		if(s->lport == recid)
285 		if(ipcmp(s->raddr, dst) == 0){
286 			bp = concatblock(bp);
287 			if(bp != nil)
288 				qpass(s->rq, bp);
289 			return;
290 		}
291 	}
292 	freeblist(bp);
293 }
294 
295 static Block *
mkechoreply(Block * bp)296 mkechoreply(Block *bp)
297 {
298 	Icmp	*q;
299 	uchar	ip[4];
300 
301 	q = (Icmp *)bp->rp;
302 	q->vihl = IP_VER4;
303 	memmove(ip, q->src, sizeof(q->dst));
304 	memmove(q->src, q->dst, sizeof(q->src));
305 	memmove(q->dst, ip,  sizeof(q->dst));
306 	q->type = EchoReply;
307 	memset(q->cksum, 0, sizeof(q->cksum));
308 	hnputs(q->cksum, ptclcsum(bp, ICMP_IPSIZE, blocklen(bp) - ICMP_IPSIZE));
309 
310 	return bp;
311 }
312 
313 static char *unreachcode[] =
314 {
315 [0]	"net unreachable",
316 [1]	"host unreachable",
317 [2]	"protocol unreachable",
318 [3]	"port unreachable",
319 [4]	"fragmentation needed and DF set",
320 [5]	"source route failed",
321 };
322 
323 static void
icmpiput(Proto * icmp,Ipifc *,Block * bp)324 icmpiput(Proto *icmp, Ipifc*, Block *bp)
325 {
326 	int	n, iplen;
327 	Icmp	*p;
328 	Block	*r;
329 	Proto	*pr;
330 	char	*msg;
331 	char	m2[128];
332 	Icmppriv *ipriv;
333 
334 	ipriv = icmp->priv;
335 
336 	ipriv->stats[InMsgs]++;
337 
338 	p = (Icmp *)bp->rp;
339 	netlog(icmp->f, Logicmp, "icmpiput %s (%d) %d\n",
340 		(p->type < nelem(icmpnames)? icmpnames[p->type]: ""),
341 		p->type, p->code);
342 	n = blocklen(bp);
343 	if(n < ICMP_IPSIZE+ICMP_HDRSIZE){
344 		ipriv->stats[InErrors]++;
345 		ipriv->stats[HlenErrs]++;
346 		netlog(icmp->f, Logicmp, "icmp hlen %d\n", n);
347 		goto raise;
348 	}
349 	iplen = nhgets(p->length);
350 	if(iplen > n){
351 		ipriv->stats[LenErrs]++;
352 		ipriv->stats[InErrors]++;
353 		netlog(icmp->f, Logicmp, "icmp length %d\n", iplen);
354 		goto raise;
355 	}
356 	if(ptclcsum(bp, ICMP_IPSIZE, iplen - ICMP_IPSIZE)){
357 		ipriv->stats[InErrors]++;
358 		ipriv->stats[CsumErrs]++;
359 		netlog(icmp->f, Logicmp, "icmp checksum error\n");
360 		goto raise;
361 	}
362 	if(p->type <= Maxtype)
363 		ipriv->in[p->type]++;
364 
365 	switch(p->type) {
366 	case EchoRequest:
367 		if (iplen < n)
368 			bp = trimblock(bp, 0, iplen);
369 		r = mkechoreply(concatblock(bp));
370 		ipriv->out[EchoReply]++;
371 		ipoput4(icmp->f, r, 0, MAXTTL, DFLTTOS, nil);
372 		break;
373 	case Unreachable:
374 		if(p->code > 5)
375 			msg = unreachcode[1];
376 		else
377 			msg = unreachcode[p->code];
378 
379 		bp->rp += ICMP_IPSIZE+ICMP_HDRSIZE;
380 		if(blocklen(bp) < MinAdvise){
381 			ipriv->stats[LenErrs]++;
382 			goto raise;
383 		}
384 		p = (Icmp *)bp->rp;
385 		pr = Fsrcvpcolx(icmp->f, p->proto);
386 		if(pr != nil && pr->advise != nil) {
387 			(*pr->advise)(pr, bp, msg);
388 			return;
389 		}
390 
391 		bp->rp -= ICMP_IPSIZE+ICMP_HDRSIZE;
392 		goticmpkt(icmp, bp);
393 		break;
394 	case TimeExceed:
395 		if(p->code == 0){
396 			snprint(m2, sizeof m2, "ttl exceeded at %V", p->src);
397 
398 			bp->rp += ICMP_IPSIZE+ICMP_HDRSIZE;
399 			if(blocklen(bp) < MinAdvise){
400 				ipriv->stats[LenErrs]++;
401 				goto raise;
402 			}
403 			p = (Icmp *)bp->rp;
404 			pr = Fsrcvpcolx(icmp->f, p->proto);
405 			if(pr != nil && pr->advise != nil) {
406 				(*pr->advise)(pr, bp, m2);
407 				return;
408 			}
409 			bp->rp -= ICMP_IPSIZE+ICMP_HDRSIZE;
410 		}
411 
412 		goticmpkt(icmp, bp);
413 		break;
414 	default:
415 		goticmpkt(icmp, bp);
416 		break;
417 	}
418 	return;
419 
420 raise:
421 	freeblist(bp);
422 }
423 
424 void
icmpadvise(Proto * icmp,Block * bp,char * msg)425 icmpadvise(Proto *icmp, Block *bp, char *msg)
426 {
427 	Conv	**c, *s;
428 	Icmp	*p;
429 	uchar	dst[IPaddrlen];
430 	ushort	recid;
431 
432 	p = (Icmp *) bp->rp;
433 	v4tov6(dst, p->dst);
434 	recid = nhgets(p->icmpid);
435 
436 	for(c = icmp->conv; *c; c++) {
437 		s = *c;
438 		if(s->lport == recid)
439 		if(ipcmp(s->raddr, dst) == 0){
440 			qhangup(s->rq, msg);
441 			qhangup(s->wq, msg);
442 			break;
443 		}
444 	}
445 	freeblist(bp);
446 }
447 
448 int
icmpstats(Proto * icmp,char * buf,int len)449 icmpstats(Proto *icmp, char *buf, int len)
450 {
451 	Icmppriv *priv;
452 	char *p, *e;
453 	int i;
454 
455 	priv = icmp->priv;
456 	p = buf;
457 	e = p+len;
458 	for(i = 0; i < Nstats; i++)
459 		p = seprint(p, e, "%s: %lud\n", statnames[i], priv->stats[i]);
460 	for(i = 0; i <= Maxtype; i++){
461 		if(icmpnames[i])
462 			p = seprint(p, e, "%s: %lud %lud\n", icmpnames[i], priv->in[i], priv->out[i]);
463 		else
464 			p = seprint(p, e, "%d: %lud %lud\n", i, priv->in[i], priv->out[i]);
465 	}
466 	return p - buf;
467 }
468 
469 void
icmpinit(Fs * fs)470 icmpinit(Fs *fs)
471 {
472 	Proto *icmp;
473 
474 	icmp = smalloc(sizeof(Proto));
475 	icmp->priv = smalloc(sizeof(Icmppriv));
476 	icmp->name = "icmp";
477 	icmp->connect = icmpconnect;
478 	icmp->announce = icmpannounce;
479 	icmp->state = icmpstate;
480 	icmp->create = icmpcreate;
481 	icmp->close = icmpclose;
482 	icmp->rcv = icmpiput;
483 	icmp->stats = icmpstats;
484 	icmp->ctl = nil;
485 	icmp->advise = icmpadvise;
486 	icmp->gc = nil;
487 	icmp->ipproto = IP_ICMPPROTO;
488 	icmp->nc = 128;
489 	icmp->ptclsize = 0;
490 
491 	Fsproto(fs, icmp);
492 }
493