blob: 486cfdb5451a314f606b356a0355e0b55492ecb7 [file] [log] [blame]
/* Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
* Portions Copyright © 1997-1999 Vita Nuova Limited
* Portions Copyright © 2000-2007 Vita Nuova Holdings Limited
* (www.vitanuova.com)
* Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
*
* Modified for the Akaros operating system:
* Copyright (c) 2013-2014 The Regents of the University of California
* Copyright (c) 2013-2015 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. */
#include <slab.h>
#include <kmalloc.h>
#include <kref.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <error.h>
#include <cpio.h>
#include <pmap.h>
#include <smp.h>
#include <net/ip.h>
#include <slab.h>
#include <kmalloc.h>
#include <kref.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <error.h>
#include <cpio.h>
#include <pmap.h>
#include <smp.h>
#include <net/ip.h>
typedef struct Icmp {
uint8_t vihl; /* Version and header length */
uint8_t tos; /* Type of service */
uint8_t length[2]; /* packet length */
uint8_t id[2]; /* Identification */
uint8_t frag[2]; /* Fragment information */
uint8_t ttl; /* Time to live */
uint8_t proto; /* Protocol */
uint8_t ipcksum[2]; /* Header checksum */
uint8_t src[4]; /* Ip source */
uint8_t dst[4]; /* Ip destination */
uint8_t type;
uint8_t code;
uint8_t cksum[2];
uint8_t icmpid[2];
uint8_t seq[2];
uint8_t data[1];
} Icmp;
enum { /* Packet Types */
EchoReply = 0,
Unreachable = 3,
SrcQuench = 4,
Redirect = 5,
EchoRequest = 8,
TimeExceed = 11,
InParmProblem = 12,
Timestamp = 13,
TimestampReply = 14,
InfoRequest = 15,
InfoReply = 16,
AddrMaskRequest = 17,
AddrMaskReply = 18,
Maxtype = 18,
};
enum {
MinAdvise = 24, /* minimum needed for us to advise another protocol */
};
char *icmpnames[Maxtype + 1] = {
[EchoReply] "EchoReply",
[Unreachable] "Unreachable",
[SrcQuench] "SrcQuench",
[Redirect] "Redirect",
[EchoRequest] "EchoRequest",
[TimeExceed] "TimeExceed",
[InParmProblem] "InParmProblem",
[Timestamp] "Timestamp",
[TimestampReply] "TimestampReply",
[InfoRequest] "InfoRequest",
[InfoReply] "InfoReply",
[AddrMaskRequest] "AddrMaskRequest",
[AddrMaskReply] "AddrMaskReply ",
};
enum {
IP_ICMPPROTO = 1,
ICMP_IPSIZE = 20,
ICMP_HDRSIZE = 8,
};
enum {
InMsgs,
InErrors,
OutMsgs,
CsumErrs,
LenErrs,
HlenErrs,
Nstats,
};
static char *statnames[Nstats] = {
[InMsgs] "InMsgs",
[InErrors] "InErrors",
[OutMsgs] "OutMsgs",
[CsumErrs] "CsumErrs",
[LenErrs] "LenErrs",
[HlenErrs] "HlenErrs",
};
typedef struct Icmppriv Icmppriv;
struct Icmppriv {
uint32_t stats[Nstats];
/* message counts */
uint32_t in[Maxtype + 1];
uint32_t out[Maxtype + 1];
};
static void icmpkick(void *x, struct block *);
static void icmpcreate(struct conv *c)
{
c->rq = qopen(64 * 1024, Qmsg, 0, c);
c->wq = qbypass(icmpkick, c);
}
void icmpconnect(struct conv *c, char **argv, int argc)
{
Fsstdconnect(c, argv, argc);
Fsconnected(c, 0);
}
extern int icmpstate(struct conv *c, char *state, int n)
{
return snprintf(state, n, "%s qin %d qout %d\n", "Datagram",
c->rq ? qlen(c->rq) : 0, c->wq ? qlen(c->wq) : 0);
}
void icmpannounce(struct conv *c, char **argv, int argc)
{
Fsstdannounce(c, argv, argc);
Fsconnected(c, NULL);
}
extern void icmpclose(struct conv *c)
{
qclose(c->rq);
qclose(c->wq);
ipmove(c->laddr, IPnoaddr);
ipmove(c->raddr, IPnoaddr);
c->lport = 0;
}
static void icmpkick(void *x, struct block *bp)
{
struct conv *c = x;
Icmp *p;
Icmppriv *ipriv;
if (bp == NULL)
return;
bp = pullupblock(bp, ICMP_IPSIZE + ICMP_HDRSIZE);
if (bp == 0)
return;
p = (Icmp *) (bp->rp);
p->vihl = IP_VER4;
ipriv = c->p->priv;
if (p->type <= Maxtype)
ipriv->out[p->type]++;
v6tov4(p->dst, c->raddr);
v6tov4(p->src, c->laddr);
p->proto = IP_ICMPPROTO;
hnputs(p->icmpid, c->lport);
memset(p->cksum, 0, sizeof(p->cksum));
hnputs(p->cksum, ptclcsum(bp, ICMP_IPSIZE, blocklen(bp) - ICMP_IPSIZE));
ipriv->stats[OutMsgs]++;
netlog(c->p->f, Logicmp,
"icmp output: Type %s (%d,%d), To %V, TTL %d, ID %d, SEQ %d\n",
icmpnames[MIN(p->type, Maxtype)], p->type, p->code, p->dst,
p->ttl, nhgets(p->icmpid), nhgets(p->seq));
ipoput4(c->p->f, bp, 0, c->ttl, c->tos, NULL);
}
extern void icmpttlexceeded(struct Fs *f, uint8_t * ia, struct block *bp)
{
struct block *nbp;
Icmp *p, *np;
p = (Icmp *) bp->rp;
netlog(f, Logicmp, "sending icmpttlexceeded -> %V\n", p->src);
nbp = block_alloc(ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8,
MEM_WAIT);
nbp->wp += ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8;
np = (Icmp *) nbp->rp;
np->vihl = IP_VER4;
memmove(np->dst, p->src, sizeof(np->dst));
v6tov4(np->src, ia);
memmove(np->data, bp->rp, ICMP_IPSIZE + 8);
np->type = TimeExceed;
np->code = 0;
np->proto = IP_ICMPPROTO;
hnputs(np->icmpid, 0);
hnputs(np->seq, 0);
memset(np->cksum, 0, sizeof(np->cksum));
hnputs(np->cksum, ptclcsum(nbp, ICMP_IPSIZE,
blocklen(nbp) - ICMP_IPSIZE));
ipoput4(f, nbp, 0, MAXTTL, DFLTTOS, NULL);
}
static void icmpunreachable(struct Fs *f, struct block *bp, int code, int seq)
{
struct block *nbp;
Icmp *p, *np;
int i;
uint8_t addr[IPaddrlen];
p = (Icmp *) bp->rp;
/* only do this for unicast sources and destinations */
v4tov6(addr, p->dst);
i = ipforme(f, addr);
if ((i & Runi) == 0)
return;
v4tov6(addr, p->src);
i = ipforme(f, addr);
if (i != 0 && (i & Runi) == 0)
return;
/* TODO: Clean this up or remove it. This is for things like UDP port
* unreachable. But we might not be UDP, due to how the code is built.
* Check the UDP netlog if you see this. */
netlog(f, Logicmp, "sending icmpnoconv -> %V\n", p->src);
nbp = block_alloc(ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8,
MEM_WAIT);
nbp->wp += ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8;
np = (Icmp *) nbp->rp;
np->vihl = IP_VER4;
memmove(np->dst, p->src, sizeof(np->dst));
memmove(np->src, p->dst, sizeof(np->src));
memmove(np->data, bp->rp, ICMP_IPSIZE + 8);
np->type = Unreachable;
np->code = code;
np->proto = IP_ICMPPROTO;
hnputs(np->icmpid, 0);
hnputs(np->seq, seq);
memset(np->cksum, 0, sizeof(np->cksum));
hnputs(np->cksum, ptclcsum(nbp, ICMP_IPSIZE,
blocklen(nbp) - ICMP_IPSIZE));
ipoput4(f, nbp, 0, MAXTTL, DFLTTOS, NULL);
}
extern void icmpnoconv(struct Fs *f, struct block *bp)
{
icmpunreachable(f, bp, 3, 0);
}
extern void icmpcantfrag(struct Fs *f, struct block *bp, int mtu)
{
icmpunreachable(f, bp, 4, mtu);
}
static void goticmpkt(struct Proto *icmp, struct block *bp)
{
struct conv **c, *s;
Icmp *p;
uint8_t dst[IPaddrlen];
uint16_t recid;
p = (Icmp *) bp->rp;
v4tov6(dst, p->src);
recid = nhgets(p->icmpid);
for (c = icmp->conv; *c; c++) {
s = *c;
if (s->lport == recid)
if (ipcmp(s->raddr, dst) == 0) {
bp = concatblock(bp);
if (bp != NULL)
qpass(s->rq, bp);
return;
}
}
freeblist(bp);
}
static struct block *mkechoreply(struct Proto *icmp, struct block *bp)
{
Icmp *q;
uint8_t ip[4];
/* we're repurposing bp to send it back out. we need to remove any
* inbound checksum flags (which were saying the HW did the checksum)
* and any other metadata. We might need to fill in some of the
* metadata too. */
block_reset_metadata(bp);
q = (Icmp *) bp->rp;
q->vihl = IP_VER4;
memmove(ip, q->src, sizeof(q->dst));
memmove(q->src, q->dst, sizeof(q->src));
memmove(q->dst, ip, sizeof(q->dst));
q->type = EchoReply;
memset(q->cksum, 0, sizeof(q->cksum));
hnputs(q->cksum, ptclcsum(bp, ICMP_IPSIZE, blocklen(bp) - ICMP_IPSIZE));
netlog(icmp->f, Logicmp,
"icmp echo reply: To %V, TTL %d, ID %d, SEQ %d\n",
q->dst, q->ttl, nhgets(q->icmpid), nhgets(q->seq));
return bp;
}
static char *unreachcode[] = {
[0] "net unreachable",
[1] "host unreachable",
[2] "protocol unreachable",
[3] "port unreachable",
[4] "fragmentation needed and DF set",
[5] "source route failed",
};
static void icmpiput(struct Proto *icmp, struct Ipifc *unused, struct block *bp)
{
int n, iplen;
Icmp *p;
struct block *r;
struct Proto *pr;
char *msg;
char m2[128];
Icmppriv *ipriv;
bp = pullupblock(bp, ICMP_IPSIZE + ICMP_HDRSIZE);
if (bp == NULL)
return;
ipriv = icmp->priv;
ipriv->stats[InMsgs]++;
p = (Icmp *) bp->rp;
/* The ID and SEQ are only for Echo Request and Reply, but close enough.
*/
netlog(icmp->f, Logicmp,
"icmp input: Type %s (%d,%d), From %V, TTL %d, ID %d, SEQ %d\n",
icmpnames[MIN(p->type, Maxtype)], p->type, p->code, p->src,
p->ttl, nhgets(p->icmpid), nhgets(p->seq));
n = blocklen(bp);
if (n < ICMP_IPSIZE + ICMP_HDRSIZE) {
/* pullupblock should fail if dlen < size. b->len >= b->dlen */
panic("We did a pullupblock and thought we had enough!");
ipriv->stats[InErrors]++;
ipriv->stats[HlenErrs]++;
netlog(icmp->f, Logicmp, "icmp hlen %d\n", n);
goto raise;
}
iplen = nhgets(p->length);
if (iplen > n || (iplen % 1)) {
ipriv->stats[LenErrs]++;
ipriv->stats[InErrors]++;
netlog(icmp->f, Logicmp, "icmp length %d\n", iplen);
goto raise;
}
if (ptclcsum(bp, ICMP_IPSIZE, iplen - ICMP_IPSIZE)) {
ipriv->stats[InErrors]++;
ipriv->stats[CsumErrs]++;
netlog(icmp->f, Logicmp, "icmp checksum error\n");
goto raise;
}
if (p->type <= Maxtype)
ipriv->in[p->type]++;
switch (p->type) {
case EchoRequest:
if (iplen < n)
bp = trimblock(bp, 0, iplen);
r = mkechoreply(icmp, bp);
ipriv->out[EchoReply]++;
ipoput4(icmp->f, r, 0, MAXTTL, DFLTTOS, NULL);
break;
case Unreachable:
if (p->code > 5)
msg = unreachcode[1];
else
msg = unreachcode[p->code];
bp->rp += ICMP_IPSIZE + ICMP_HDRSIZE;
if (blocklen(bp) < MinAdvise) {
ipriv->stats[LenErrs]++;
goto raise;
}
p = (Icmp *) bp->rp;
pr = Fsrcvpcolx(icmp->f, p->proto);
if (pr != NULL && pr->advise != NULL) {
(*pr->advise) (pr, bp, msg);
return;
}
bp->rp -= ICMP_IPSIZE + ICMP_HDRSIZE;
goticmpkt(icmp, bp);
break;
case TimeExceed:
if (p->code == 0) {
snprintf(m2, sizeof(m2), "ttl exceeded at %V", p->src);
bp->rp += ICMP_IPSIZE + ICMP_HDRSIZE;
if (blocklen(bp) < MinAdvise) {
ipriv->stats[LenErrs]++;
goto raise;
}
p = (Icmp *) bp->rp;
pr = Fsrcvpcolx(icmp->f, p->proto);
if (pr != NULL && pr->advise != NULL) {
(*pr->advise) (pr, bp, m2);
return;
}
bp->rp -= ICMP_IPSIZE + ICMP_HDRSIZE;
}
goticmpkt(icmp, bp);
break;
default:
goticmpkt(icmp, bp);
break;
}
return;
raise:
freeblist(bp);
}
void icmpadvise(struct Proto *icmp, struct block *bp, char *msg)
{
struct conv **c, *s;
Icmp *p;
uint8_t dst[IPaddrlen];
uint16_t recid;
p = (Icmp *) bp->rp;
v4tov6(dst, p->dst);
recid = nhgets(p->icmpid);
for (c = icmp->conv; *c; c++) {
s = *c;
if (s->lport == recid)
if (ipcmp(s->raddr, dst) == 0) {
qhangup(s->rq, msg);
qhangup(s->wq, msg);
break;
}
}
freeblist(bp);
}
int icmpstats(struct Proto *icmp, char *buf, int len)
{
Icmppriv *priv;
char *p, *e;
int i;
priv = icmp->priv;
p = buf;
e = p + len;
for (i = 0; i < Nstats; i++)
p = seprintf(p, e, "%s: %u\n", statnames[i], priv->stats[i]);
for (i = 0; i <= Maxtype; i++) {
if (icmpnames[i])
p = seprintf(p, e, "%s: %u %u\n", icmpnames[i],
priv->in[i], priv->out[i]);
else
p = seprintf(p, e, "%d: %u %u\n", i, priv->in[i],
priv->out[i]);
}
return p - buf;
}
void icmpinit(struct Fs *fs)
{
struct Proto *icmp;
icmp = kzmalloc(sizeof(struct Proto), 0);
icmp->priv = kzmalloc(sizeof(Icmppriv), 0);
icmp->name = "icmp";
icmp->connect = icmpconnect;
icmp->announce = icmpannounce;
icmp->state = icmpstate;
icmp->create = icmpcreate;
icmp->close = icmpclose;
icmp->rcv = icmpiput;
icmp->stats = icmpstats;
icmp->ctl = NULL;
icmp->advise = icmpadvise;
icmp->gc = NULL;
icmp->ipproto = IP_ICMPPROTO;
icmp->nc = 128;
icmp->ptclsize = 0;
Fsproto(fs, icmp);
}