c125490ee41b7c74eab0d9a45300390582a9a0fa
[akaros.git] / kern / src / net / icmp.c
1 /* Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
2  * Portions Copyright © 1997-1999 Vita Nuova Limited
3  * Portions Copyright © 2000-2007 Vita Nuova Holdings Limited
4  *                                (www.vitanuova.com)
5  * Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
6  *
7  * Modified for the Akaros operating system:
8  * Copyright (c) 2013-2014 The Regents of the University of California
9  * Copyright (c) 2013-2015 Google Inc.
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a copy
12  * of this software and associated documentation files (the "Software"), to deal
13  * in the Software without restriction, including without limitation the rights
14  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15  * copies of the Software, and to permit persons to whom the Software is
16  * furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included in
19  * all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
24  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27  * SOFTWARE. */
28
29 #include <vfs.h>
30 #include <kfs.h>
31 #include <slab.h>
32 #include <kmalloc.h>
33 #include <kref.h>
34 #include <string.h>
35 #include <stdio.h>
36 #include <assert.h>
37 #include <error.h>
38 #include <cpio.h>
39 #include <pmap.h>
40 #include <smp.h>
41 #include <ip.h>
42
43 #include <vfs.h>
44 #include <kfs.h>
45 #include <slab.h>
46 #include <kmalloc.h>
47 #include <kref.h>
48 #include <string.h>
49 #include <stdio.h>
50 #include <assert.h>
51 #include <error.h>
52 #include <cpio.h>
53 #include <pmap.h>
54 #include <smp.h>
55 #include <ip.h>
56
57 typedef struct Icmp {
58         uint8_t vihl;                           /* Version and header length */
59         uint8_t tos;                            /* Type of service */
60         uint8_t length[2];                      /* packet length */
61         uint8_t id[2];                          /* Identification */
62         uint8_t frag[2];                        /* Fragment information */
63         uint8_t ttl;                            /* Time to live */
64         uint8_t proto;                          /* Protocol */
65         uint8_t ipcksum[2];                     /* Header checksum */
66         uint8_t src[4];                         /* Ip source */
67         uint8_t dst[4];                         /* Ip destination */
68         uint8_t type;
69         uint8_t code;
70         uint8_t cksum[2];
71         uint8_t icmpid[2];
72         uint8_t seq[2];
73         uint8_t data[1];
74 } Icmp;
75
76 enum {                                                  /* Packet Types */
77         EchoReply = 0,
78         Unreachable = 3,
79         SrcQuench = 4,
80         Redirect = 5,
81         EchoRequest = 8,
82         TimeExceed = 11,
83         InParmProblem = 12,
84         Timestamp = 13,
85         TimestampReply = 14,
86         InfoRequest = 15,
87         InfoReply = 16,
88         AddrMaskRequest = 17,
89         AddrMaskReply = 18,
90
91         Maxtype = 18,
92 };
93
94 enum {
95         MinAdvise = 24,                         /* minimum needed for us to advise another protocol */
96 };
97
98 char *icmpnames[Maxtype + 1] = {
99         [EchoReply] "EchoReply",
100         [Unreachable] "Unreachable",
101         [SrcQuench] "SrcQuench",
102         [Redirect] "Redirect",
103         [EchoRequest] "EchoRequest",
104         [TimeExceed] "TimeExceed",
105         [InParmProblem] "InParmProblem",
106         [Timestamp] "Timestamp",
107         [TimestampReply] "TimestampReply",
108         [InfoRequest] "InfoRequest",
109         [InfoReply] "InfoReply",
110         [AddrMaskRequest] "AddrMaskRequest",
111         [AddrMaskReply] "AddrMaskReply  ",
112 };
113
114 enum {
115         IP_ICMPPROTO = 1,
116         ICMP_IPSIZE = 20,
117         ICMP_HDRSIZE = 8,
118 };
119
120 enum {
121         InMsgs,
122         InErrors,
123         OutMsgs,
124         CsumErrs,
125         LenErrs,
126         HlenErrs,
127
128         Nstats,
129 };
130
131 static char *statnames[Nstats] = {
132         [InMsgs] "InMsgs",
133         [InErrors] "InErrors",
134         [OutMsgs] "OutMsgs",
135         [CsumErrs] "CsumErrs",
136         [LenErrs] "LenErrs",
137         [HlenErrs] "HlenErrs",
138 };
139
140 typedef struct Icmppriv Icmppriv;
141 struct Icmppriv {
142         uint32_t stats[Nstats];
143
144         /* message counts */
145         uint32_t in[Maxtype + 1];
146         uint32_t out[Maxtype + 1];
147 };
148
149 static void icmpkick(void *x, struct block *);
150
151 static void icmpcreate(struct conv *c)
152 {
153         c->rq = qopen(64 * 1024, Qmsg, 0, c);
154         c->wq = qbypass(icmpkick, c);
155 }
156
157 extern char *icmpconnect(struct conv *c, char **argv, int argc)
158 {
159         char *e;
160
161         e = Fsstdconnect(c, argv, argc);
162         if (e != NULL)
163                 return e;
164         Fsconnected(c, e);
165
166         return NULL;
167 }
168
169 extern int icmpstate(struct conv *c, char *state, int n)
170 {
171         return snprintf(state, n, "%s qin %d qout %d\n",
172                                         "Datagram",
173                                         c->rq ? qlen(c->rq) : 0, c->wq ? qlen(c->wq) : 0);
174 }
175
176 extern char *icmpannounce(struct conv *c, char **argv, int argc)
177 {
178         char *e;
179
180         e = Fsstdannounce(c, argv, argc);
181         if (e != NULL)
182                 return e;
183         Fsconnected(c, NULL);
184
185         return NULL;
186 }
187
188 extern void icmpclose(struct conv *c)
189 {
190         qclose(c->rq);
191         qclose(c->wq);
192         ipmove(c->laddr, IPnoaddr);
193         ipmove(c->raddr, IPnoaddr);
194         c->lport = 0;
195 }
196
197 static void icmpkick(void *x, struct block *bp)
198 {
199         struct conv *c = x;
200         Icmp *p;
201         Icmppriv *ipriv;
202
203         if (bp == NULL)
204                 return;
205
206         bp = pullupblock(bp, ICMP_IPSIZE + ICMP_HDRSIZE);
207         if (bp == 0)
208                 return;
209         p = (Icmp *) (bp->rp);
210         p->vihl = IP_VER4;
211         ipriv = c->p->priv;
212         if (p->type <= Maxtype)
213                 ipriv->out[p->type]++;
214
215         v6tov4(p->dst, c->raddr);
216         v6tov4(p->src, c->laddr);
217         p->proto = IP_ICMPPROTO;
218         hnputs(p->icmpid, c->lport);
219         memset(p->cksum, 0, sizeof(p->cksum));
220         hnputs(p->cksum, ptclcsum(bp, ICMP_IPSIZE, blocklen(bp) - ICMP_IPSIZE));
221         ipriv->stats[OutMsgs]++;
222         ipoput4(c->p->f, bp, 0, c->ttl, c->tos, NULL);
223 }
224
225 extern void icmpttlexceeded(struct Fs *f, uint8_t * ia, struct block *bp)
226 {
227         struct block *nbp;
228         Icmp *p, *np;
229
230         p = (Icmp *) bp->rp;
231
232         netlog(f, Logicmp, "sending icmpttlexceeded -> %V\n", p->src);
233         nbp = allocb(ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8);
234         nbp->wp += ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8;
235         np = (Icmp *) nbp->rp;
236         np->vihl = IP_VER4;
237         memmove(np->dst, p->src, sizeof(np->dst));
238         v6tov4(np->src, ia);
239         memmove(np->data, bp->rp, ICMP_IPSIZE + 8);
240         np->type = TimeExceed;
241         np->code = 0;
242         np->proto = IP_ICMPPROTO;
243         hnputs(np->icmpid, 0);
244         hnputs(np->seq, 0);
245         memset(np->cksum, 0, sizeof(np->cksum));
246         hnputs(np->cksum, ptclcsum(nbp, ICMP_IPSIZE, blocklen(nbp) - ICMP_IPSIZE));
247         ipoput4(f, nbp, 0, MAXTTL, DFLTTOS, NULL);
248
249 }
250
251 static void icmpunreachable(struct Fs *f, struct block *bp, int code, int seq)
252 {
253         struct block *nbp;
254         Icmp *p, *np;
255         int i;
256         uint8_t addr[IPaddrlen];
257
258         p = (Icmp *) bp->rp;
259
260         /* only do this for unicast sources and destinations */
261         v4tov6(addr, p->dst);
262         i = ipforme(f, addr);
263         if ((i & Runi) == 0)
264                 return;
265         v4tov6(addr, p->src);
266         i = ipforme(f, addr);
267         if (i != 0 && (i & Runi) == 0)
268                 return;
269
270         netlog(f, Logicmp, "sending icmpnoconv -> %V\n", p->src);
271         nbp = allocb(ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8);
272         nbp->wp += ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8;
273         np = (Icmp *) nbp->rp;
274         np->vihl = IP_VER4;
275         memmove(np->dst, p->src, sizeof(np->dst));
276         memmove(np->src, p->dst, sizeof(np->src));
277         memmove(np->data, bp->rp, ICMP_IPSIZE + 8);
278         np->type = Unreachable;
279         np->code = code;
280         np->proto = IP_ICMPPROTO;
281         hnputs(np->icmpid, 0);
282         hnputs(np->seq, seq);
283         memset(np->cksum, 0, sizeof(np->cksum));
284         hnputs(np->cksum, ptclcsum(nbp, ICMP_IPSIZE, blocklen(nbp) - ICMP_IPSIZE));
285         ipoput4(f, nbp, 0, MAXTTL, DFLTTOS, NULL);
286 }
287
288 extern void icmpnoconv(struct Fs *f, struct block *bp)
289 {
290         icmpunreachable(f, bp, 3, 0);
291 }
292
293 extern void icmpcantfrag(struct Fs *f, struct block *bp, int mtu)
294 {
295         icmpunreachable(f, bp, 4, mtu);
296 }
297
298 static void goticmpkt(struct Proto *icmp, struct block *bp)
299 {
300         struct conv **c, *s;
301         Icmp *p;
302         uint8_t dst[IPaddrlen];
303         uint16_t recid;
304
305         p = (Icmp *) bp->rp;
306         v4tov6(dst, p->src);
307         recid = nhgets(p->icmpid);
308
309         for (c = icmp->conv; *c; c++) {
310                 s = *c;
311                 if (s->lport == recid)
312                         if (ipcmp(s->raddr, dst) == 0) {
313                                 bp = concatblock(bp);
314                                 if (bp != NULL)
315                                         qpass(s->rq, bp);
316                                 return;
317                         }
318         }
319         freeblist(bp);
320 }
321
322 static struct block *mkechoreply(struct block *bp)
323 {
324         Icmp *q;
325         uint8_t ip[4];
326
327         /* we're repurposing bp to send it back out.  we need to remove any inbound
328          * checksum flags (which were saying the HW did the checksum) */
329         bp->flag &= ~BCKSUM_FLAGS;
330         q = (Icmp *) bp->rp;
331         q->vihl = IP_VER4;
332         memmove(ip, q->src, sizeof(q->dst));
333         memmove(q->src, q->dst, sizeof(q->src));
334         memmove(q->dst, ip, sizeof(q->dst));
335         q->type = EchoReply;
336         memset(q->cksum, 0, sizeof(q->cksum));
337         hnputs(q->cksum, ptclcsum(bp, ICMP_IPSIZE, blocklen(bp) - ICMP_IPSIZE));
338
339         return bp;
340 }
341
342 static char *unreachcode[] = {
343         [0] "net unreachable",
344         [1] "host unreachable",
345         [2] "protocol unreachable",
346         [3] "port unreachable",
347         [4] "fragmentation needed and DF set",
348         [5] "source route failed",
349 };
350
351 static void icmpiput(struct Proto *icmp, struct Ipifc *unused, struct block *bp)
352 {
353         int n, iplen;
354         Icmp *p;
355         struct block *r;
356         struct Proto *pr;
357         char *msg;
358         char m2[128];
359         Icmppriv *ipriv;
360
361         bp = pullupblock(bp, ICMP_IPSIZE + ICMP_HDRSIZE);
362         if (bp == NULL)
363                 return;
364
365         ipriv = icmp->priv;
366
367         ipriv->stats[InMsgs]++;
368
369         p = (Icmp *) bp->rp;
370         netlog(icmp->f, Logicmp, "icmpiput %d %d\n", p->type, p->code);
371         n = blocklen(bp);
372         if (n < ICMP_IPSIZE + ICMP_HDRSIZE) {
373                 ipriv->stats[InErrors]++;
374                 ipriv->stats[HlenErrs]++;
375                 netlog(icmp->f, Logicmp, "icmp hlen %d\n", n);
376                 goto raise;
377         }
378         iplen = nhgets(p->length);
379         if (iplen > n || (iplen % 1)) {
380                 ipriv->stats[LenErrs]++;
381                 ipriv->stats[InErrors]++;
382                 netlog(icmp->f, Logicmp, "icmp length %d\n", iplen);
383                 goto raise;
384         }
385         if (ptclcsum(bp, ICMP_IPSIZE, iplen - ICMP_IPSIZE)) {
386                 ipriv->stats[InErrors]++;
387                 ipriv->stats[CsumErrs]++;
388                 netlog(icmp->f, Logicmp, "icmp checksum error\n");
389                 goto raise;
390         }
391         if (p->type <= Maxtype)
392                 ipriv->in[p->type]++;
393
394         switch (p->type) {
395                 case EchoRequest:
396                         if (iplen < n)
397                                 bp = trimblock(bp, 0, iplen);
398                         r = mkechoreply(bp);
399                         ipriv->out[EchoReply]++;
400                         ipoput4(icmp->f, r, 0, MAXTTL, DFLTTOS, NULL);
401                         break;
402                 case Unreachable:
403                         if (p->code > 5)
404                                 msg = unreachcode[1];
405                         else
406                                 msg = unreachcode[p->code];
407
408                         bp->rp += ICMP_IPSIZE + ICMP_HDRSIZE;
409                         if (blocklen(bp) < MinAdvise) {
410                                 ipriv->stats[LenErrs]++;
411                                 goto raise;
412                         }
413                         p = (Icmp *) bp->rp;
414                         pr = Fsrcvpcolx(icmp->f, p->proto);
415                         if (pr != NULL && pr->advise != NULL) {
416                                 (*pr->advise) (pr, bp, msg);
417                                 return;
418                         }
419
420                         bp->rp -= ICMP_IPSIZE + ICMP_HDRSIZE;
421                         goticmpkt(icmp, bp);
422                         break;
423                 case TimeExceed:
424                         if (p->code == 0) {
425                                 snprintf(m2, sizeof(m2), "ttl exceeded at %V", p->src);
426
427                                 bp->rp += ICMP_IPSIZE + ICMP_HDRSIZE;
428                                 if (blocklen(bp) < MinAdvise) {
429                                         ipriv->stats[LenErrs]++;
430                                         goto raise;
431                                 }
432                                 p = (Icmp *) bp->rp;
433                                 pr = Fsrcvpcolx(icmp->f, p->proto);
434                                 if (pr != NULL && pr->advise != NULL) {
435                                         (*pr->advise) (pr, bp, m2);
436                                         return;
437                                 }
438                                 bp->rp -= ICMP_IPSIZE + ICMP_HDRSIZE;
439                         }
440
441                         goticmpkt(icmp, bp);
442                         break;
443                 default:
444                         goticmpkt(icmp, bp);
445                         break;
446         }
447         return;
448
449 raise:
450         freeblist(bp);
451 }
452
453 void icmpadvise(struct Proto *icmp, struct block *bp, char *msg)
454 {
455         struct conv **c, *s;
456         Icmp *p;
457         uint8_t dst[IPaddrlen];
458         uint16_t recid;
459
460         p = (Icmp *) bp->rp;
461         v4tov6(dst, p->dst);
462         recid = nhgets(p->icmpid);
463
464         for (c = icmp->conv; *c; c++) {
465                 s = *c;
466                 if (s->lport == recid)
467                         if (ipcmp(s->raddr, dst) == 0) {
468                                 qhangup(s->rq, msg);
469                                 qhangup(s->wq, msg);
470                                 break;
471                         }
472         }
473         freeblist(bp);
474 }
475
476 int icmpstats(struct Proto *icmp, char *buf, int len)
477 {
478         Icmppriv *priv;
479         char *p, *e;
480         int i;
481
482         priv = icmp->priv;
483         p = buf;
484         e = p + len;
485         for (i = 0; i < Nstats; i++)
486                 p = seprintf(p, e, "%s: %u\n", statnames[i], priv->stats[i]);
487         for (i = 0; i <= Maxtype; i++) {
488                 if (icmpnames[i])
489                         p = seprintf(p, e, "%s: %u %u\n", icmpnames[i], priv->in[i],
490                                                  priv->out[i]);
491                 else
492                         p = seprintf(p, e, "%d: %u %u\n", i, priv->in[i], priv->out[i]);
493         }
494         return p - buf;
495 }
496
497 void icmpinit(struct Fs *fs)
498 {
499         struct Proto *icmp;
500
501         icmp = kzmalloc(sizeof(struct Proto), 0);
502         icmp->priv = kzmalloc(sizeof(Icmppriv), 0);
503         icmp->name = "icmp";
504         icmp->connect = icmpconnect;
505         icmp->announce = icmpannounce;
506         icmp->state = icmpstate;
507         icmp->create = icmpcreate;
508         icmp->close = icmpclose;
509         icmp->rcv = icmpiput;
510         icmp->stats = icmpstats;
511         icmp->ctl = NULL;
512         icmp->advise = icmpadvise;
513         icmp->gc = NULL;
514         icmp->ipproto = IP_ICMPPROTO;
515         icmp->nc = 128;
516         icmp->ptclsize = 0;
517
518         Fsproto(fs, icmp);
519 }