/* * 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); }