Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Internet Control Message Protocol for IPv6
 */
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "ip.h"
#include "ipv6.h"

enum
{
        InMsgs6,
        InErrors6,
        OutMsgs6,
        CsumErrs6,
        LenErrs6,
        HlenErrs6,
        HoplimErrs6,
        IcmpCodeErrs6,
        TargetErrs6,
        OptlenErrs6,
        AddrmxpErrs6,
        RouterAddrErrs6,

        Nstats6,
};

enum {
        ICMP_USEAD6     = 40,
};

enum {
        Oflag   = 1<<5,
        Sflag   = 1<<6,
        Rflag   = 1<<7,
};

enum {
        /* ICMPv6 types */
        EchoReply       = 0,
        UnreachableV6   = 1,
        PacketTooBigV6  = 2,
        TimeExceedV6    = 3,
        SrcQuench       = 4,
        ParamProblemV6  = 4,
        Redirect        = 5,
        EchoRequest     = 8,
        TimeExceed      = 11,
        InParmProblem   = 12,
        Timestamp       = 13,
        TimestampReply  = 14,
        InfoRequest     = 15,
        InfoReply       = 16,
        AddrMaskRequest = 17,
        AddrMaskReply   = 18,
        EchoRequestV6   = 128,
        EchoReplyV6     = 129,
        RouterSolicit   = 133,
        RouterAdvert    = 134,
        NbrSolicit      = 135,
        NbrAdvert       = 136,
        RedirectV6      = 137,

        Maxtype6        = 137,
};

/* on-the-wire packet formats */
typedef struct IPICMP IPICMP;
typedef struct Ndpkt Ndpkt;
typedef struct NdiscC NdiscC;

/* we do this to avoid possible struct padding  */
#define ICMPHDR \
        IPV6HDR; \
        uchar   type; \
        uchar   code; \
        uchar   cksum[2]; \
        uchar   icmpid[2]; \
        uchar   seq[2]

struct IPICMP {
        ICMPHDR;
        uchar   payload[];
};

#define IPICMPSZ offsetof(IPICMP, payload[0])

struct NdiscC {
        ICMPHDR;
        uchar   target[IPaddrlen];
        uchar   payload[];
};

#define NDISCSZ offsetof(NdiscC, payload[0])

struct Ndpkt {
        ICMPHDR;
        uchar   target[IPaddrlen];
        uchar   otype;
        uchar   olen;           /* length in units of 8 octets(incl type, code),
                                 * 1 for IEEE 802 addresses */
        uchar   lnaddr[6];      /* link-layer address */
        uchar   payload[];
};

#define NDPKTSZ offsetof(Ndpkt, payload[0])

typedef struct Icmppriv6
{
        ulong   stats[Nstats6];

        /* message counts */
        ulong   in[Maxtype6+1];
        ulong   out[Maxtype6+1];
} Icmppriv6;

typedef struct Icmpcb6
{
        QLock;
        uchar   headers;
} Icmpcb6;

char *icmpnames6[Maxtype6+1] =
{
[EchoReply]             "EchoReply",
[UnreachableV6]         "UnreachableV6",
[PacketTooBigV6]        "PacketTooBigV6",
[TimeExceedV6]          "TimeExceedV6",
[SrcQuench]             "SrcQuench",
[Redirect]              "Redirect",
[EchoRequest]           "EchoRequest",
[TimeExceed]            "TimeExceed",
[InParmProblem]         "InParmProblem",
[Timestamp]             "Timestamp",
[TimestampReply]        "TimestampReply",
[InfoRequest]           "InfoRequest",
[InfoReply]             "InfoReply",
[AddrMaskRequest]       "AddrMaskRequest",
[AddrMaskReply]         "AddrMaskReply",
[EchoRequestV6]         "EchoRequestV6",
[EchoReplyV6]           "EchoReplyV6",
[RouterSolicit]         "RouterSolicit",
[RouterAdvert]          "RouterAdvert",
[NbrSolicit]            "NbrSolicit",
[NbrAdvert]             "NbrAdvert",
[RedirectV6]            "RedirectV6",
};

static char *statnames6[Nstats6] =
{
[InMsgs6]       "InMsgs",
[InErrors6]     "InErrors",
[OutMsgs6]      "OutMsgs",
[CsumErrs6]     "CsumErrs",
[LenErrs6]      "LenErrs",
[HlenErrs6]     "HlenErrs",
[HoplimErrs6]   "HoplimErrs",
[IcmpCodeErrs6] "IcmpCodeErrs",
[TargetErrs6]   "TargetErrs",
[OptlenErrs6]   "OptlenErrs",
[AddrmxpErrs6]  "AddrmxpErrs",
[RouterAddrErrs6]       "RouterAddrErrs",
};

static char *unreachcode[] =
{
[Icmp6_no_route]        "no route to destination",
[Icmp6_ad_prohib]       "comm with destination administratively prohibited",
[Icmp6_out_src_scope]   "beyond scope of source address",
[Icmp6_adr_unreach]     "address unreachable",
[Icmp6_port_unreach]    "port unreachable",
[Icmp6_gress_src_fail]  "source address failed ingress/egress policy",
[Icmp6_rej_route]       "reject route to destination",
[Icmp6_unknown]         "icmp unreachable: unknown code",
};

static void icmpkick6(void *x, Block *bp);

static void
icmpcreate6(Conv *c)
{
        c->rq = qopen(64*1024, Qmsg, 0, c);
        c->wq = qbypass(icmpkick6, c);
}

static void
set_cksum(Block *bp)
{
        IPICMP *p = (IPICMP *)(bp->rp);

        hnputl(p->vcf, 0);      /* borrow IP header as pseudoheader */
        hnputs(p->ploadlen, blocklen(bp) - IP6HDR);
        p->proto = 0;
        p->ttl = ICMPv6;        /* ttl gets set later */
        hnputs(p->cksum, 0);
        hnputs(p->cksum, ptclcsum(bp, 0, blocklen(bp)));
        p->proto = ICMPv6;
}

static Block *
newIPICMP(int packetlen)
{
        Block *nbp;

        nbp = allocb(packetlen);
        nbp->wp += packetlen;
        memset(nbp->rp, 0, packetlen);
        return nbp;
}

void
icmpadvise6(Proto *icmp, Block *bp, char *msg)
{
        ushort recid;
        Conv **c, *s;
        IPICMP *p;

        p = (IPICMP *)bp->rp;
        recid = nhgets(p->icmpid);

        for(c = icmp->conv; *c; c++) {
                s = *c;
                if(s->lport == recid && ipcmp(s->raddr, p->dst) == 0){
                        qhangup(s->rq, msg);
                        qhangup(s->wq, msg);
                        break;
                }
        }
        freeblist(bp);
}

static void
icmpkick6(void *x, Block *bp)
{
        uchar laddr[IPaddrlen], raddr[IPaddrlen];
        Conv *c = x;
        IPICMP *p;
        Icmppriv6 *ipriv = c->p->priv;
        Icmpcb6 *icb = (Icmpcb6*)c->ptcl;

        if(bp == nil)
                return;

        if(icb->headers==6) {
                /* get user specified addresses */
                bp = pullupblock(bp, ICMP_USEAD6);
                if(bp == nil)
                        return;
                bp->rp += 8;
                ipmove(laddr, bp->rp);
                bp->rp += IPaddrlen;
                ipmove(raddr, bp->rp);
                bp->rp += IPaddrlen;
                bp = padblock(bp, IP6HDR);
        }

        if(blocklen(bp) < IPICMPSZ){
                freeblist(bp);
                return;
        }
        p = (IPICMP *)(bp->rp);
        if(icb->headers == 6) {
                ipmove(p->dst, raddr);
                ipmove(p->src, laddr);
        } else {
                ipmove(p->dst, c->raddr);
                ipmove(p->src, c->laddr);
                hnputs(p->icmpid, c->lport);
        }

        set_cksum(bp);
        p->vcf[0] = 0x06 << 4;
        if(p->type <= Maxtype6)
                ipriv->out[p->type]++;
        ipoput6(c->p->f, bp, 0, c->ttl, c->tos, nil);
}

char*
icmpctl6(Conv *c, char **argv, int argc)
{
        Icmpcb6 *icb;

        icb = (Icmpcb6*) c->ptcl;
        if(argc==1 && strcmp(argv[0], "headers")==0) {
                icb->headers = 6;
                return nil;
        }
        return "unknown control request";
}

static void
goticmpkt6(Proto *icmp, Block *bp, int muxkey)
{
        ushort recid;
        uchar *addr;
        Conv **c, *s;
        IPICMP *p = (IPICMP *)bp->rp;

        if(muxkey == 0) {
                recid = nhgets(p->icmpid);
                addr = p->src;
        } else {
                recid = muxkey;
                addr = p->dst;
        }

        for(c = icmp->conv; *c; c++){
                s = *c;
                if(s->lport == recid && ipcmp(s->raddr, addr) == 0){
                        bp = concatblock(bp);
                        if(bp != nil)
                                qpass(s->rq, bp);
                        return;
                }
        }

        freeblist(bp);
}

static Block *
mkechoreply6(Block *bp, Ipifc *ifc)
{
        uchar addr[IPaddrlen];
        IPICMP *p = (IPICMP *)(bp->rp);

        ipmove(addr, p->src);
        if(!isv6mcast(p->dst))
                ipmove(p->src, p->dst);
        else if (!ipv6anylocal(ifc, p->src))
                return nil;
        ipmove(p->dst, addr);
        p->type = EchoReplyV6;
        set_cksum(bp);
        return bp;
}

/*
 * sends out an ICMPv6 neighbor solicitation
 *      suni == SRC_UNSPEC or SRC_UNI,
 *      tuni == TARG_MULTI => multicast for address resolution,
 *      and tuni == TARG_UNI => neighbor reachability.
 */
extern void
icmpns(Fs *f, uchar* src, int suni, uchar* targ, int tuni, uchar* mac)
{
        Block *nbp;
        Ndpkt *np;
        Proto *icmp = f->t2p[ICMPv6];
        Icmppriv6 *ipriv = icmp->priv;

        nbp = newIPICMP(NDPKTSZ);
        np = (Ndpkt*) nbp->rp;

        if(suni == SRC_UNSPEC)
                memmove(np->src, v6Unspecified, IPaddrlen);
        else
                memmove(np->src, src, IPaddrlen);

        if(tuni == TARG_UNI)
                memmove(np->dst, targ, IPaddrlen);
        else
                ipv62smcast(np->dst, targ);

        np->type = NbrSolicit;
        np->code = 0;
        memmove(np->target, targ, IPaddrlen);
        if(suni != SRC_UNSPEC) {
                np->otype = SRC_LLADDR;
                np->olen = 1;           /* 1+1+6 = 8 = 1 8-octet */
                memmove(np->lnaddr, mac, sizeof(np->lnaddr));
        } else
                nbp->wp -= NDPKTSZ - NDISCSZ;

        set_cksum(nbp);
        np = (Ndpkt*)nbp->rp;
        np->ttl = HOP_LIMIT;
        np->vcf[0] = 0x06 << 4;
        ipriv->out[NbrSolicit]++;
        netlog(f, Logicmp, "sending neighbor solicitation %I\n", targ);
        ipoput6(f, nbp, 0, MAXTTL, DFLTTOS, nil);
}

/*
 * sends out an ICMPv6 neighbor advertisement. pktflags == RSO flags.
 */
extern void
icmpna(Fs *f, uchar* src, uchar* dst, uchar* targ, uchar* mac, uchar flags)
{
        Block *nbp;
        Ndpkt *np;
        Proto *icmp = f->t2p[ICMPv6];
        Icmppriv6 *ipriv = icmp->priv;

        nbp = newIPICMP(NDPKTSZ);
        np = (Ndpkt*)nbp->rp;

        memmove(np->src, src, IPaddrlen);
        memmove(np->dst, dst, IPaddrlen);

        np->type = NbrAdvert;
        np->code = 0;
        np->icmpid[0] = flags;
        memmove(np->target, targ, IPaddrlen);

        np->otype = TARGET_LLADDR;
        np->olen = 1;
        memmove(np->lnaddr, mac, sizeof(np->lnaddr));

        set_cksum(nbp);
        np = (Ndpkt*) nbp->rp;
        np->ttl = HOP_LIMIT;
        np->vcf[0] = 0x06 << 4;
        ipriv->out[NbrAdvert]++;
        netlog(f, Logicmp, "sending neighbor advertisement %I\n", src);
        ipoput6(f, nbp, 0, MAXTTL, DFLTTOS, nil);
}

/* if free is true, freeblist(bp) before return. */
extern void
icmphostunr(Fs *f, Ipifc *ifc, Block *bp, int code, int free)
{
        int osz, sz;
        Block *nbp;
        IPICMP *np;
        Icmppriv6 *ipriv;
        Ip6hdr *p;
        Proto *icmp;

        osz = BLEN(bp);
        sz = MIN(IPICMPSZ + osz, v6MINTU);
        icmp = f->t2p[ICMPv6];
        ipriv = icmp->priv;
        p = (Ip6hdr *)bp->rp;
        if(isv6mcast(p->src))
                goto freebl;
        nbp = newIPICMP(sz);
        np = (IPICMP *)nbp->rp;

        rlock(ifc);
        if(!ipv6anylocal(ifc, np->src)){
                netlog(f, Logicmp, "icmphostunr fail -> src %I dst %I\n",
                        p->src, p->dst);
                runlock(ifc);
                freeblist(nbp);
                goto freebl;
        }

        netlog(f, Logicmp, "send icmphostunr -> src %I dst %I\n", p->src, p->dst);
        memmove(np->dst, p->src, IPaddrlen);
        np->type = UnreachableV6;
        np->code = code;
        memmove(nbp->rp + IPICMPSZ, bp->rp, sz - IPICMPSZ);
        set_cksum(nbp);
        np->ttl = HOP_LIMIT;
        np->vcf[0] = 0x06 << 4;
        ipriv->out[UnreachableV6]++;

        if(free)
                ipiput6(f, ifc, nbp);
        else
                ipoput6(f, nbp, 0, MAXTTL, DFLTTOS, nil);
        runlock(ifc);
freebl:
        if(free)
                freeblist(bp);
}

extern void
icmpttlexceeded6(Fs *f, Ipifc *ifc, Block *bp)
{
        int osz = BLEN(bp);
        int sz = MIN(IPICMPSZ + osz, v6MINTU);
        Block *nbp;
        IPICMP *np;
        Ip6hdr *p;
        Proto *icmp = f->t2p[ICMPv6];
        Icmppriv6 *ipriv = icmp->priv;

        p = (Ip6hdr *)bp->rp;
        if(isv6mcast(p->src))
                return;

        nbp = newIPICMP(sz);
        np = (IPICMP *) nbp->rp;
        if(ipv6anylocal(ifc, np->src))
                netlog(f, Logicmp, "send icmpttlexceeded6 -> src %I dst %I\n",
                        p->src, p->dst);
        else {
                netlog(f, Logicmp, "icmpttlexceeded6 fail -> src %I dst %I\n",
                        p->src, p->dst);
                return;
        }

        memmove(np->dst, p->src, IPaddrlen);
        np->type = TimeExceedV6;
        np->code = 0;
        memmove(nbp->rp + IPICMPSZ, bp->rp, sz - IPICMPSZ);
        set_cksum(nbp);
        np->ttl = HOP_LIMIT;
        np->vcf[0] = 0x06 << 4;
        ipriv->out[TimeExceedV6]++;
        ipoput6(f, nbp, 0, MAXTTL, DFLTTOS, nil);
}

extern void
icmppkttoobig6(Fs *f, Ipifc *ifc, Block *bp)
{
        int osz = BLEN(bp);
        int sz = MIN(IPICMPSZ + osz, v6MINTU);
        Block *nbp;
        IPICMP *np;
        Ip6hdr *p;
        Proto *icmp = f->t2p[ICMPv6];
        Icmppriv6 *ipriv = icmp->priv;

        p = (Ip6hdr *)bp->rp;
        if(isv6mcast(p->src))
                return;

        nbp = newIPICMP(sz);
        np = (IPICMP *)nbp->rp;
        if(ipv6anylocal(ifc, np->src))
                netlog(f, Logicmp, "send icmppkttoobig6 -> src %I dst %I\n",
                        p->src, p->dst);
        else {
                netlog(f, Logicmp, "icmppkttoobig6 fail -> src %I dst %I\n",
                        p->src, p->dst);
                return;
        }

        memmove(np->dst, p->src, IPaddrlen);
        np->type = PacketTooBigV6;
        np->code = 0;
        hnputl(np->icmpid, ifc->maxtu - ifc->m->hsize);
        memmove(nbp->rp + IPICMPSZ, bp->rp, sz - IPICMPSZ);
        set_cksum(nbp);
        np->ttl = HOP_LIMIT;
        np->vcf[0] = 0x06 << 4;
        ipriv->out[PacketTooBigV6]++;
        ipoput6(f, nbp, 0, MAXTTL, DFLTTOS, nil);
}

/*
 * RFC 2461, pages 39-40, pages 57-58.
 */
static int
valid(Proto *icmp, Ipifc *, Block *bp, Icmppriv6 *ipriv)
{
        int sz, osz, unsp, n, ttl, iplen, pktsz;
        uchar *packet;
        IPICMP *p;
        Ndpkt *np;

        n = blocklen(bp);
        if(n < IPICMPSZ) {
                ipriv->stats[HlenErrs6]++;
                netlog(icmp->f, Logicmp, "icmp hlen %d\n", n);
                goto err;
        }

        packet = bp->rp;
        p = (IPICMP *)packet;
        pktsz = BLEN(bp);
        iplen = nhgets(p->ploadlen);
        if(iplen > n - IP6HDR) {
                ipriv->stats[LenErrs6]++;
                netlog(icmp->f, Logicmp, "icmp length %d\n", iplen);
                goto err;
        }

        /* Rather than construct explicit pseudoheader, overwrite IPv6 header */
        if(p->proto != ICMPv6) {
                /* This code assumes no extension headers!!! */
                netlog(icmp->f, Logicmp, "icmp error: extension header\n");
                goto err;
        }
        memset(packet, 0, 4);
        ttl = p->ttl;
        p->ttl = p->proto;
        p->proto = 0;
        if(ptclcsum(bp, 0, iplen + IP6HDR)) {
                ipriv->stats[CsumErrs6]++;
                netlog(icmp->f, Logicmp, "icmp checksum error\n");
                goto err;
        }
        p->proto = p->ttl;
        p->ttl = ttl;

        /* additional tests for some pkt types */
        if (p->type != NbrSolicit   && p->type != NbrAdvert &&
            p->type != RouterAdvert && p->type != RouterSolicit &&
            p->type != RedirectV6)
                return 1;       /* TODO: unknown, presumed valid; why? */
        if(p->ttl != HOP_LIMIT) {
                ipriv->stats[HoplimErrs6]++;
                goto err;
        }
        if(p->code != 0) {
                ipriv->stats[IcmpCodeErrs6]++;
                goto err;
        }

        switch (p->type) {
        case NbrSolicit:
        case NbrAdvert:
                np = (Ndpkt*) p;
                if(isv6mcast(np->target)) {
                        ipriv->stats[TargetErrs6]++;
                        goto err;
                }
                if(optexsts(np) && np->olen == 0) {
                        ipriv->stats[OptlenErrs6]++;
                        goto err;
                }
                if (p->type == NbrSolicit && ipcmp(np->src, v6Unspecified) == 0)
                        if(!issmcast(np->dst) || optexsts(np)) {
                                ipriv->stats[AddrmxpErrs6]++;
                                goto err;
                        }
                if(p->type == NbrAdvert && isv6mcast(np->dst) &&
                    nhgets(np->icmpid) & Sflag){
                        ipriv->stats[AddrmxpErrs6]++;
                        goto err;
                }
                break;
        case RouterAdvert:
                if(pktsz - IP6HDR < 16) {
                        ipriv->stats[HlenErrs6]++;
                        goto err;
                }
                if(!islinklocal(p->src)) {
                        ipriv->stats[RouterAddrErrs6]++;
                        goto err;
                }
                for (sz = IPICMPSZ + 8; sz+1 < pktsz; sz += 8*osz) {
                        osz = packet[sz+1];
                        if(osz <= 0) {
                                ipriv->stats[OptlenErrs6]++;
                                goto err;
                        }
                }
                break;
        case RouterSolicit:
                if(pktsz - IP6HDR < 8) {
                        ipriv->stats[HlenErrs6]++;
                        goto err;
                }
                unsp = (ipcmp(p->src, v6Unspecified) == 0);
                for (sz = IPICMPSZ + 8; sz+1 < pktsz; sz += 8*osz) {
                        osz = packet[sz+1];
                        if(osz <= 0 || (unsp && packet[sz] == SRC_LLADDR)) {
                                ipriv->stats[OptlenErrs6]++;
                                goto err;
                        }
                }
                break;
        case RedirectV6:
                /* TODO: fill in */
                break;
        default:
                goto err;
        }
        return 1;
err:
        ipriv->stats[InErrors6]++;
        return 0;
}

static int
targettype(Fs *f, Ipifc *ifc, uchar *target)
{
        Iplifc *lifc;
        int t;

        rlock(ifc);
        if(ipproxyifc(f, ifc, target)) {
                runlock(ifc);
                return Tuniproxy;
        }

        for(lifc = ifc->lifc; lifc; lifc = lifc->next)
                if(ipcmp(lifc->local, target) == 0) {
                        t = (lifc->tentative)? Tunitent: Tunirany;
                        runlock(ifc);
                        return t;
                }

        runlock(ifc);
        return 0;
}

/* bp needs to be freed with freeblist or passed on. */
static void
icmpiput6(Proto *icmp, Ipifc *ipifc, Block *bp)
{
        int type;
        char *msg, m2[128];
        uchar pktflags;
        uchar *packet, *src;
        uchar lsrc[IPaddrlen];
        Block *r;
        IPICMP *p;
        Icmppriv6 *ipriv;
        Iplifc *lifc;
        Ndpkt* np;
        Proto *pr;

        packet = bp->rp;
        p = (IPICMP *)packet;
        type = p->type;
        ipriv = icmp->priv;
        if(!valid(icmp, ipifc, bp, ipriv) || type > Maxtype6)
                goto raise;

        ipriv->in[type]++;
        switch(type) {
        case EchoRequestV6:
                bp = concatblock(bp);
                r = mkechoreply6(bp, ipifc);
                if(r == nil)
                        goto raise;
                ipriv->out[EchoReply]++;
                ipoput6(icmp->f, r, 0, MAXTTL, DFLTTOS, nil);
                break;
        case UnreachableV6:
                if(p->code >= nelem(unreachcode))
                        msg = unreachcode[Icmp6_unknown];
                else
                        msg = unreachcode[p->code];

                bp->rp += IPICMPSZ;
                if(blocklen(bp) < 8){
                        ipriv->stats[LenErrs6]++;
                        goto raise;
                }
                p = (IPICMP *)bp->rp;
                pr = Fsrcvpcolx(icmp->f, p->proto);
                if(pr != nil && pr->advise != nil) {
                        (*pr->advise)(pr, bp, msg);
                        return;
                }

                bp->rp -= IPICMPSZ;
                goticmpkt6(icmp, bp, 0);
                break;
        case TimeExceedV6:
                if(p->code == 0){
                        snprint(m2, sizeof m2, "ttl exceeded at %I", p->src);
                        bp->rp += IPICMPSZ;
                        if(blocklen(bp) < 8){
                                ipriv->stats[LenErrs6]++;
                                goto raise;
                        }
                        p = (IPICMP *)bp->rp;
                        pr = Fsrcvpcolx(icmp->f, p->proto);
                        if(pr && pr->advise) {
                                (*pr->advise)(pr, bp, m2);
                                return;
                        }
                        bp->rp -= IPICMPSZ;
                }
                goticmpkt6(icmp, bp, 0);
                break;
        case RouterAdvert:
        case RouterSolicit:
                /* using lsrc as a temp, munge hdr for goticmp6 */
                if (0) {
                        memmove(lsrc, p->src, IPaddrlen);
                        memmove(p->src, p->dst, IPaddrlen);
                        memmove(p->dst, lsrc, IPaddrlen);
                }
                goticmpkt6(icmp, bp, type);
                break;
        case NbrSolicit:
                np = (Ndpkt*)p;                 /* within bp */
                pktflags = 0;
                switch (targettype(icmp->f, ipifc, np->target)) {
                case Tunirany:
                        pktflags |= Oflag;
                        /* fall through */
                case Tuniproxy:
                        if(ipcmp(np->src, v6Unspecified) != 0) {
                                arpenter(icmp->f, V6, np->src, np->lnaddr,
                                        8*np->olen-2, 0);
                                pktflags |= Sflag;
                        }
                        if(ipv6local(ipifc, lsrc)) {
                                src = np->src;
                                if(ipcmp(src, v6Unspecified) == 0)
                                        src = v6allnodesL;
                                icmpna(icmp->f, lsrc, src, np->target,
                                        ipifc->mac, pktflags);
                        }
                        break;
                case Tunitent:
                        /*
                         * not clear what needs to be done.  send up
                         * an icmp mesg saying `don't use this address'?
                         */
                        break;
                }
                freeblist(bp);
                break;
        case NbrAdvert:
                /*
                 * if the target address matches one of the local interface
                 * addresses and the local interface address has tentative bit
                 * set, insert into ARP table. this is so the duplicate address
                 * detection part of ipconfig can discover duplication through
                 * the arp table.
                 */
                np = (Ndpkt*)p;                 /* within bp */
                lifc = iplocalonifc(ipifc, np->target);
                arpenter(icmp->f, V6, np->target, np->lnaddr, 8*np->olen-2,
                        lifc && lifc->tentative);
                freeblist(bp);
                break;
        case PacketTooBigV6:
        default:
                goticmpkt6(icmp, bp, 0);
                break;
        }
        return;

raise:
        freeblist(bp);
}

int
icmpstats6(Proto *icmp6, char *buf, int len)
{
        Icmppriv6 *priv;
        char *p, *e;
        int i;

        priv = icmp6->priv;
        p = buf;
        e = p+len;
        for(i = 0; i < Nstats6; i++)
                p = seprint(p, e, "%s: %lud\n", statnames6[i], priv->stats[i]);
        for(i = 0; i <= Maxtype6; i++)
                if(icmpnames6[i])
                        p = seprint(p, e, "%s: %lud %lud\n", icmpnames6[i],
                                priv->in[i], priv->out[i]);
                else if (0)
                        p = seprint(p, e, "%d: %lud %lud\n", i, priv->in[i],
                                priv->out[i]);
        return p - buf;
}

/* import from icmp.c */
extern int      icmpstate(Conv *c, char *state, int n);
extern char*    icmpannounce(Conv *c, char **argv, int argc);
extern char*    icmpconnect(Conv *c, char **argv, int argc);
extern void     icmpclose(Conv *c);

void
icmp6init(Fs *fs)
{
        Proto *icmp6 = smalloc(sizeof(Proto));

        icmp6->priv = smalloc(sizeof(Icmppriv6));
        icmp6->name = "icmpv6";
        icmp6->connect = icmpconnect;
        icmp6->announce = icmpannounce;
        icmp6->state = icmpstate;
        icmp6->create = icmpcreate6;
        icmp6->close = icmpclose;
        icmp6->rcv = icmpiput6;
        icmp6->stats = icmpstats6;
        icmp6->ctl = icmpctl6;
        icmp6->advise = icmpadvise6;
        icmp6->gc = nil;
        icmp6->ipproto = ICMPv6;
        icmp6->nc = 16;
        icmp6->ptclsize = sizeof(Icmpcb6);

        Fsproto(fs, icmp6);
}