#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "ip.h"
typedef struct Icmp {
uchar vihl; /* Version and header length */
uchar tos; /* Type of service */
uchar length[2]; /* packet length */
uchar id[2]; /* Identification */
uchar frag[2]; /* Fragment information */
uchar ttl; /* Time to live */
uchar proto; /* Protocol */
uchar ipcksum[2]; /* Header checksum */
uchar src[4]; /* Ip source */
uchar dst[4]; /* Ip destination */
uchar type;
uchar code;
uchar cksum[2];
uchar icmpid[2];
uchar seq[2];
uchar data[1];
} Icmp;
enum { /* Packet Types */
EchoReply = 0,
Unreachable = 3,
SrcQuench = 4,
Redirect = 4,
EchoRequest = 8,
TimeExceed = 11,
InParmProblem = 12,
Timestamp = 13,
TimestampReply = 14,
InfoRequest = 15,
InfoReply = 16,
AddrMaskRequest = 17,
AddrMaskReply = 18,
Maxtype = 18,
};
enum {
IP_ICMPPROTO = 1,
ICMP_IPSIZE = 20,
ICMP_HDRSIZE = 8,
};
typedef struct Icmpstats Icmpstats;
struct Icmpstats
{
ulong icmpInMsgs;
ulong icmpInErrors;
// ulong icmpInDestUnreachs;
// ulong icmpInTimeExcds;
// ulong icmpInParmProbs;
// ulong icmpInSrcQuenchs;
// ulong icmpInRedirects;
// ulong icmpInEchos;
// ulong icmpInEchoReps;
// ulong icmpInTimestamps;
// ulong icmpInTimestampReps;
// ulong icmpInAddrMasks;
// ulong icmpInAddrMaskReps;
ulong icmpOutMsgs;
ulong icmpOutErrors;
// ulong icmpOutDestUnreachs;
// ulong icmpOutTimeExcds;
// ulong icmpOutParmProbs;
// ulong icmpOutSrcQuenchs;
// ulong icmpOutRedirects;
// ulong icmpOutEchos;
// ulong icmpOutEchoReps;
// ulong icmpOutTimestamps;
// ulong icmpOutTimestampReps;
// ulong icmpOutAddrMasks;
// ulong icmpOutAddrMaskReps;
};
typedef struct Icmppriv Icmppriv;
struct Icmppriv
{
/* MIB Counters */
Icmpstats istats;
/* non-MIB stats */
ulong csumerr; /* checksum errors */
ulong lenerr; /* short packet */
ulong hlenerr; /* short header */
ulong in[Maxtype+1];
ulong out[Maxtype+1];
};
static char*
icmpconnect(Conv *c, char **argv, int argc)
{
char *e;
e = Fsstdconnect(c, argv, argc);
Fsconnected(c, e);
return e;
}
static int
icmpstate(Conv *c, char *state, int n)
{
USED(c);
return snprint(state, n, "%s", "Datagram");
}
static void
icmpcreate(Conv *c)
{
c->rq = qopen(64*1024, 0, 0, c);
c->wq = qopen(64*1024, 0, 0, 0);
}
static char*
icmpannounce(Conv *c, char **argv, int argc)
{
char *e;
e = Fsstdannounce(c, argv, argc);
if(e != nil);
return e;
Fsconnected(c, nil);
return nil;
}
static void
icmpclose(Conv *c)
{
qclose(c->rq);
qclose(c->wq);
ipmove(c->laddr, IPnoaddr);
ipmove(c->raddr, IPnoaddr);
c->lport = 0;
unlock(c);
}
static void
icmpkick(Conv *c, int l)
{
Icmp *p;
Block *bp;
Icmppriv *ipriv;
USED(l);
bp = qget(c->wq);
if(bp == nil)
return;
if(blocklen(bp) < ICMP_IPSIZE + ICMP_HDRSIZE){
freeblist(bp);
return;
}
p = (Icmp *)(bp->rp);
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->istats.icmpOutMsgs++;
ipoput(c->p->f, bp, 0, c->ttl);
}
extern void
icmpttlexceeded(Fs *f, uchar *ia, Block *bp)
{
Block *nbp;
Icmp *p, *np;
p = (Icmp *)bp->rp;
netlog(f, Logicmp, "sending icmpttlexceeded -> %V\n", p->src);
nbp = allocb(ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8);
nbp->wp += ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8;
np = (Icmp *)nbp->rp;
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));
/* stats.out[TimeExceed]++; */
ipoput(f, nbp, 0, MAXTTL);
}
extern void
icmpnoconv(Fs *f, Block *bp)
{
Block *nbp;
Icmp *p, *np;
int i;
uchar 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;
netlog(f, Logicmp, "sending icmpnoconv -> %V\n", p->src);
nbp = allocb(ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8);
nbp->wp += ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8;
np = (Icmp *)nbp->rp;
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 = 3;
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));
/* stats.out[Unreachable]++; */
ipoput(f, nbp, 0, MAXTTL);
}
static void
goticmpkt(Proto *icmp, Block *bp)
{
Conv **c, *s;
Icmp *p;
uchar dst[IPaddrlen];
ushort 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){
qpass(s->rq, bp);
return;
}
}
freeblist(bp);
}
static Block *
mkechoreply(Block *bp)
{
Icmp *q;
uchar ip[4];
q = (Icmp *)bp->rp;
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));
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(Proto *icmp, uchar*, Block *bp)
{
int n, iplen;
Icmp *p;
Block *r;
Proto *pr;
char *msg;
char m2[128];
Icmppriv *ipriv;
ipriv = icmp->priv;
ipriv->istats.icmpInMsgs++;
p = (Icmp *)bp->rp;
netlog(icmp->f, Logicmp, "icmpiput %d %d\n", p->type, p->code);
n = blocklen(bp);
if(n < ICMP_IPSIZE+ICMP_HDRSIZE){
ipriv->istats.icmpInErrors++;
ipriv->hlenerr++;
netlog(icmp->f, Logicmp, "icmp hlen %d\n", n);
goto raise;
}
iplen = nhgets(p->length);
if(iplen > n || (iplen % 1)){
ipriv->lenerr++;
ipriv->istats.icmpInErrors++;
netlog(icmp->f, Logicmp, "icmp length %d\n", iplen);
goto raise;
}
if(ptclcsum(bp, ICMP_IPSIZE, iplen - ICMP_IPSIZE)){
ipriv->istats.icmpInErrors++;
ipriv->csumerr++;
netlog(icmp->f, Logicmp, "icmp checksum error\n");
goto raise;
}
if(p->type <= Maxtype)
ipriv->in[p->type]++;
switch(p->type) {
case EchoRequest:
r = mkechoreply(bp);
ipriv->out[EchoReply]++;
ipoput(icmp->f, r, 0, MAXTTL);
break;
case Unreachable:
if(p->code > 5 || p->code < 0)
msg = unreachcode[1];
else
msg = unreachcode[p->code];
bp->rp += ICMP_IPSIZE+ICMP_HDRSIZE;
if(blocklen(bp) < 8){
ipriv->lenerr++;
goto raise;
}
p = (Icmp *)bp->rp;
pr = Fsrcvpcolx(icmp->f, p->proto);
if(pr != nil && pr->advise != nil) {
(*pr->advise)(pr, bp, msg);
return;
}
bp->rp -= ICMP_IPSIZE+ICMP_HDRSIZE;
goticmpkt(icmp, bp);
break;
case TimeExceed:
if(p->code == 0){
sprint(m2, "ttl exceeded at %V", p->src);
bp->rp += ICMP_IPSIZE+ICMP_HDRSIZE;
if(blocklen(bp) < 8){
ipriv->lenerr++;
goto raise;
}
p = (Icmp *)bp->rp;
pr = Fsrcvpcolx(icmp->f, p->proto);
if(pr != nil && pr->advise != nil) {
(*pr->advise)(pr, bp, m2);
return;
}
}
goticmpkt(icmp, bp);
break;
default:
goticmpkt(icmp, bp);
break;
}
return;
raise:
freeblist(bp);
}
void
icmpadvise(Proto *icmp, Block *bp, char *msg)
{
Conv **c, *s;
Icmp *p;
uchar dst[IPaddrlen];
ushort 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(Proto *icmp, char *buf, int len)
{
Icmppriv *ipriv;
ipriv = icmp->priv;
return snprint(buf,len,"%lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud %lud",
ipriv->istats.icmpInMsgs,
ipriv->istats.icmpInErrors,
ipriv->in[Unreachable],
ipriv->in[TimeExceed],
ipriv->in[InParmProblem],
ipriv->in[SrcQuench],
ipriv->in[Redirect],
ipriv->in[EchoRequest],
ipriv->in[EchoReply],
ipriv->in[Timestamp],
ipriv->in[TimestampReply],
ipriv->in[AddrMaskRequest],
ipriv->in[AddrMaskReply],
ipriv->istats.icmpOutMsgs,
ipriv->istats.icmpOutErrors,
ipriv->out[Unreachable],
ipriv->out[TimeExceed],
ipriv->out[InParmProblem],
ipriv->out[SrcQuench],
ipriv->out[Redirect],
ipriv->out[EchoRequest],
ipriv->out[EchoReply],
ipriv->out[Timestamp],
ipriv->out[TimestampReply],
ipriv->out[AddrMaskRequest],
ipriv->out[AddrMaskReply]);
}
void
icmpinit(Fs *fs)
{
Proto *icmp;
icmp = smalloc(sizeof(Proto));
icmp->priv = smalloc(sizeof(Icmppriv));
icmp->name = "icmp";
icmp->kick = icmpkick;
icmp->connect = icmpconnect;
icmp->announce = icmpannounce;
icmp->state = icmpstate;
icmp->create = icmpcreate;
icmp->close = icmpclose;
icmp->rcv = icmpiput;
icmp->stats = icmpstats;
icmp->ctl = nil;
icmp->advise = icmpadvise;
icmp->ipproto = IP_ICMPPROTO;
icmp->nc = 16;
icmp->ptclsize = 0;
Fsproto(fs, icmp);
}