code: plan9front

Download patch

ref: 98a66671cf6900b6239ac1b48638ffa835e75c27
parent: 0ed8a3bd7b1bbe2a3857f2abcaabdc4edd2d8b05
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sun Nov 20 10:38:36 EST 2022

devip: lilu dallas multicast.

Allow accepting udp "connections" using a multicast local address.
Before, it was only possible to receive multicast using the headers
option. Having a connection orirented stream can be very usefull
when receiving multicast audio data. One gets a "connection" for
every source.

Implement (optional) support for IGMPv2 and MLDv1.
This can be usefull if bridges on the network have IGMP/MLD snooping
enabled, and wont forward multicast traffic unless we report what
we excpect. This is experimental for now, so the igmp protocol
must be manually added to the kernel configuration.

--- a/sys/src/9/ip/devip.c
+++ b/sys/src/9/ip/devip.c
@@ -1173,27 +1173,27 @@
 			if(cb->nf < 2)
 				error("addmulti needs interface address");
 			if(cb->nf == 2){
-				if(!ipismulticast(c->raddr))
-					error("addmulti for a non multicast address");
 				if (parseip(ia, cb->f[1]) == -1)
 					error(Ebadip);
-				ipifcaddmulti(c, c->raddr, ia);
+				if(ipcmp(c->raddr, IPnoaddr) == 0 || ipismulticast(c->laddr))
+					ipifcaddmulti(c, c->laddr, ia);
+				else
+					ipifcaddmulti(c, c->raddr, ia);
 			} else {
 				if (parseip(ia, cb->f[1]) == -1 ||
 				    parseip(ma, cb->f[2]) == -1)
 					error(Ebadip);
-				if(!ipismulticast(ma))
-					error("addmulti for a non multicast address");
 				ipifcaddmulti(c, ma, ia);
 			}
 		} else if(strcmp(cb->f[0], "remmulti") == 0){
 			if(cb->nf < 2)
 				error("remmulti needs interface address");
-			if(!ipismulticast(c->raddr))
-				error("remmulti for a non multicast address");
 			if (parseip(ia, cb->f[1]) == -1)
 				error(Ebadip);
-			ipifcremmulti(c, c->raddr, ia);
+			if(ipcmp(c->raddr, IPnoaddr) == 0 || ipismulticast(c->laddr))
+				ipifcremmulti(c, c->laddr, ia);
+			else
+				ipifcremmulti(c, c->raddr, ia);
 		} else if(x->ctl != nil) {
 			p = (*x->ctl)(c, cb->f, cb->nf);
 			if(p != nil)
@@ -1258,7 +1258,7 @@
 
 	p->f = f;
 
-	if(p->ipproto > 0){
+	if(p->ipproto >= 0){
 		if(f->t2p[p->ipproto] != nil)
 			return -1;
 		f->t2p[p->ipproto] = p;
--- a/sys/src/9/ip/igmp.c
+++ b/sys/src/9/ip/igmp.c
@@ -1,6 +1,5 @@
 /*
- * igmp - internet group management protocol
- * unfinished.
+ * IGMPv2 - internet group management protocol (and MLDv1)
  */
 #include "u.h"
 #include "../port/lib.h"
@@ -10,6 +9,7 @@
 #include "../port/error.h"
 
 #include "ip.h"
+#include "ipv6.h"
 
 enum
 {
@@ -19,7 +19,15 @@
 
 	IGMPquery	= 1,
 	IGMPreport	= 2,
+	IGMPv2report	= 6,
+	IGMPv2leave	= 7,
 
+	IP_MLDPROTO	= HBH,		/* hop-by-hop header */
+
+	MLDquery	= 130,
+	MLDreport	= 131,
+	MLDdone		= 132,
+
 	MSPTICK		= 100,
 	MAXTIMEOUT	= 10000/MSPTICK,	/* at most 10 secs for a response */
 };
@@ -36,14 +44,14 @@
 	uchar	Unused;	
 	uchar	proto;		/* Protocol */
 	uchar	cksum[2];	/* checksum of ip portion */
-	uchar	src[IPaddrlen];		/* Ip source */
-	uchar	dst[IPaddrlen];		/* Ip destination */
+	uchar	src[4];		/* Ip source */
+	uchar	dst[4];		/* Ip destination */
 
 	/* igmp header */
 	uchar	vertype;	/* version and type */
-	uchar	unused;
-	uchar	igmpcksum[2];		/* checksum of igmp portion */
-	uchar	group[IPaddrlen];	/* multicast group */
+	uchar	resptime;
+	uchar	igmpcksum[2];	/* checksum of igmp portion */
+	uchar	group[4];	/* multicast group */
 
 	uchar	payload[];
 };
@@ -50,116 +58,177 @@
 
 #define IGMPPKTSZ offsetof(IGMPpkt, payload[0])
 
-/*
- *  lists for group reports
- */
-typedef struct IGMPrep IGMPrep;
-struct IGMPrep
-{
-	IGMPrep		*next;
-	Medium		*m;
-	int		ticks;
-	Multicast	*multi;
-};
+typedef struct MLDpkt MLDpkt;
+struct MLDpkt {
+	IPV6HDR;
 
-typedef struct IGMP IGMP;
-struct IGMP
-{
-	Lock;
-	Rendez	r;
-	IGMPrep	*reports;
+	uchar	type;
+	uchar	code;
+	uchar	cksum[2];
+	uchar	delay[2];
+	uchar	res[2];
+	uchar	group[IPaddrlen];
+	uchar	payload[];
 };
 
-IGMP igmpalloc;
+#define MLDPKTSZ offsetof(MLDpkt, payload[0])
 
-	Proto	igmp;
-extern	Fs	fs;
+static uchar mldhbhopt[] = {
+	ICMPv6,	/* NextHeader */
+	0x00,	/* HeaderLength */
+		0x05,		/* Option: Router Alert */
+		0x02,		/* Length */
+		0x00, 0x00,	/* MLD */
 
-static struct Stats
+		0x01,		/* Option: PadN */
+		0x00,		/* Length */
+};
+
+typedef struct Report Report;
+struct Report
 {
-	ulong 	inqueries;
-	ulong	outqueries;
-	ulong	inreports;
-	ulong	outreports;
-} stats;
+	Report	*next;
+	Proto	*proto;
+	Ipifc	*ifc;
+	int	ifcid;
+	int	timeout;	/* in MSPTICK's */
+	Ipmulti	*multi;
+};
 
-void
-igmpsendreport(Medium *m, uchar *addr)
+typedef struct Priv Priv;
+struct Priv
 {
+	QLock;
+	Rendez	r;
+	Report	*reports;
+};
+
+static void
+igmpsendreport(Fs *f, uchar *src, uchar *dst, uchar *group, int done)
+{
 	IGMPpkt *p;
 	Block *bp;
 
-	bp = allocb(sizeof(IGMPpkt));
-	p = (IGMPpkt*)bp->wp;
-	p->vihl = IP_VER4;
+	bp = allocb(IGMPPKTSZ);
 	bp->wp += IGMPPKTSZ;
-	memset(bp->rp, 0, IGMPPKTSZ);
-	hnputl(p->src, Mediumgetaddr(m));
-	hnputl(p->dst, Ipallsys);
-	p->vertype = (1<<4) | IGMPreport;
+	p = (IGMPpkt*)bp->rp;
+	memset(p, 0, IGMPPKTSZ);
+	p->vihl = IP_VER4;
+	memmove(p->src, src+IPv4off, IPv4addrlen);
+	memmove(p->dst, dst+IPv4off, IPv4addrlen);
+	p->vertype = (1<<4) | (done? IGMPv2leave: IGMPv2report);
+	p->resptime = 0;
 	p->proto = IP_IGMPPROTO;
-	memmove(p->group, addr, IPaddrlen);
+	memmove(p->group, group+IPv4off, IPv4addrlen);
 	hnputs(p->igmpcksum, ptclcsum(bp, IGMP_IPHDRSIZE, IGMP_HDRSIZE));
-	netlog(Logigmp, "igmpreport %I\n", p->group);
-	stats.outreports++;
-	ipoput4(bp, 0, 1, DFLTTOS, nil);	/* TTL of 1 */
+	ipoput4(f, bp, 0, 1, DFLTTOS, nil);	/* TTL of 1 */
 }
 
+static void
+mldsendreport(Fs *f, uchar *src, uchar *dst, uchar *group, int done)
+{
+	MLDpkt *p;
+	Block *bp;
+
+	if(!islinklocal(src))
+		return;
+
+	bp = allocb(sizeof(mldhbhopt)+MLDPKTSZ);
+	bp->wp += sizeof(mldhbhopt)+MLDPKTSZ;
+	bp->rp += sizeof(mldhbhopt);
+	p = (MLDpkt*)bp->rp;
+	memset(p, 0, MLDPKTSZ);
+	ipmove(p->src, src);
+	ipmove(p->dst, dst);
+	p->type = done? MLDdone: MLDreport;
+	p->code = 0;
+	ipmove(p->group, group);
+
+	/* generate checksum */
+	hnputl(p->vcf, 0);
+	hnputs(p->ploadlen, MLDPKTSZ-IP6HDR);
+	p->proto = 0;
+	p->ttl = ICMPv6;	/* ttl gets set later */
+	hnputs(p->cksum, 0);
+	hnputs(p->cksum, ptclcsum(bp, 0, MLDPKTSZ));
+
+	/* add hop-by-hop option header */
+	bp->rp -= sizeof(mldhbhopt);
+	memmove(bp->rp, p, IP6HDR);
+	memmove(bp->rp + IP6HDR, mldhbhopt, sizeof(mldhbhopt));
+	p = (MLDpkt*)bp->rp;
+	p->proto = IP_MLDPROTO;
+	hnputs(p->ploadlen, BLEN(bp) - IP6HDR);
+	
+	ipoput6(f, bp, 0, 1, DFLTTOS, nil);	/* TTL of 1 */
+}
+
+static void
+sendreport(Proto *pr, uchar *ia, uchar *group, int done)
+{
+	switch(pr->ipproto){
+	case IP_IGMPPROTO:
+		igmpsendreport(pr->f, ia, group, group, done);
+		break;
+	case IP_MLDPROTO:
+		mldsendreport(pr->f, ia, group, group, done);
+		break;
+	}
+}
+
 static int
 isreport(void *a)
 {
-	USED(a);
-	return igmpalloc.reports != 0;
+	return ((Priv*)a)->reports != 0;
 }
 
-
-void
+static void
 igmpproc(void *a)
 {
-	IGMPrep *rp, **lrp;
-	Multicast *mp, **lmp;
-	uchar ip[IPaddrlen];
+	Proto *pr, *igmp = a;
+	Priv *priv = igmp->priv;
+	Report *rp, **lrp;
+	Ipmulti *mp, **lmp;
 
-	USED(a);
-
 	for(;;){
-		sleep(&igmpalloc.r, isreport, 0);
+		sleep(&priv->r, isreport, priv);
 		for(;;){
-			lock(&igmpalloc);
-
-			if(igmpalloc.reports == nil)
+			qlock(priv);
+			if(priv->reports == nil)
 				break;
 	
 			/* look for a single report */
-			lrp = &igmpalloc.reports;
 			mp = nil;
-			for(rp = *lrp; rp; rp = *lrp){
-				rp->ticks++;
+			pr = nil;
+			lrp = &priv->reports;
+			for(rp = *lrp; rp != nil; rp = *lrp){
 				lmp = &rp->multi;
-				for(mp = *lmp; mp; mp = *lmp){
-					if(rp->ticks >= mp->timeout){
+				for(mp = *lmp; mp != nil; mp = *lmp){
+					if(rp->timeout <= 1 || nrand(rp->timeout) == 0){
 						*lmp = mp->next;
 						break;
 					}
 					lmp = &mp->next;
 				}
-				if(mp != nil)
-					break;
-
+				pr = rp->proto;
 				if(rp->multi != nil){
+					rp->timeout--;
 					lrp = &rp->next;
-					continue;
 				} else {
 					*lrp = rp->next;
 					free(rp);
 				}
+				if(mp != nil)
+					break;
 			}
-			unlock(&igmpalloc);
+			qunlock(priv);
 
-			if(mp){
+			if(mp != nil){
 				/* do a single report and try again */
-				hnputl(ip, mp->addr);
-				igmpsendreport(rp->m, ip);
+				if(pr != nil && !waserror()){
+					sendreport(pr, mp->ia, mp->ma, 0);
+					poperror();
+				}
 				free(mp);
 				continue;
 			}
@@ -166,92 +235,69 @@
 
 			tsleep(&up->sleep, return0, 0, MSPTICK);
 		}
-		unlock(&igmpalloc);
+		qunlock(priv);
 	}
-
 }
 
-void
-igmpiput(Medium *m, Ipifc *, Block *bp)
+/*
+ *  find report list for this protocol and interface
+ */
+static Report*
+findreport(Report *rp, Proto *pr, Ipifc *ifc)
 {
-	int n;
-	IGMPpkt *ghp;
-	Ipaddr group;
-	IGMPrep *rp, **lrp;
-	Multicast *mp, **lmp;
+	for(; rp != nil; rp = rp->next)
+		if(rp->proto == pr && rp->ifc == ifc && rp->ifcid == ifc->ifcid)
+			return rp;
 
-	ghp = (IGMPpkt*)(bp->rp);
-	netlog(Logigmp, "igmpiput: %d %I\n", ghp->vertype, ghp->group);
+	return nil;
+}
 
-	n = blocklen(bp);
-	if(n < IGMP_IPHDRSIZE+IGMP_HDRSIZE){
-		netlog(Logigmp, "igmpiput: bad len\n");
-		goto error;
-	}
-	if((ghp->vertype>>4) != 1){
-		netlog(Logigmp, "igmpiput: bad igmp type\n");
-		goto error;
-	}
-	if(ptclcsum(bp, IGMP_IPHDRSIZE, IGMP_HDRSIZE)){
-		netlog(Logigmp, "igmpiput: checksum error %I\n", ghp->src);
-		goto error;
-	}
+static void
+queuereport(Proto *pr, Ipifc *ifc, uchar *group, int timeout)
+{
+	Priv *priv = pr->priv;
+	Report *rp;
 
-	group = nhgetl(ghp->group);
-	
-	lock(&igmpalloc);
-	switch(ghp->vertype & 0xf){
-	case IGMPquery:
+	qlock(priv);
+	if(findreport(priv->reports, pr, ifc) != nil){
 		/*
-		 *  start reporting groups that we're a member of.
+		 *  we are already reporting on this interface,
+		 *  wait for the report to time-out.
 		 */
-		stats.inqueries++;
-		for(rp = igmpalloc.reports; rp; rp = rp->next)
-			if(rp->m == m)
-				break;
-		if(rp != nil)
-			break;	/* already reporting */
+		qunlock(priv);
+		return;
+	}
 
-		mp = Mediumcopymulti(m);
-		if(mp == nil)
-			break;
+	/*
+	 *  start reporting groups that we're a member of.
+	 */
+	rp = smalloc(sizeof(Report));
+	rp->proto = pr;
+	rp->ifc = ifc;
+	rp->ifcid = ifc->ifcid;
+	rp->timeout = (timeout < 1 || timeout > MAXTIMEOUT) ? MAXTIMEOUT : timeout;
+	rp->multi = ipifcgetmulti(pr->f, ifc, group);
 
-		rp = malloc(sizeof(*rp));
-		if(rp == nil)
-			break;
+	rp->next = priv->reports;
+	priv->reports = rp;
 
-		rp->m = m;
-		rp->multi = mp;
-		rp->ticks = 0;
-		for(; mp; mp = mp->next)
-			mp->timeout = nrand(MAXTIMEOUT);
-		rp->next = igmpalloc.reports;
-		igmpalloc.reports = rp;
+	wakeup(&priv->r);
+	qunlock(priv);
+}
 
-		wakeup(&igmpalloc.r);
+static void
+purgereport(Proto *pr, Ipifc *ifc, uchar *group)
+{
+	Priv *priv = pr->priv;
+	Report *rp;
 
-		break;
-	case IGMPreport:
-		/*
-		 *  find report list for this medium
-		 */
-		stats.inreports++;
-		lrp = &igmpalloc.reports;
-		for(rp = *lrp; rp; rp = *lrp){
-			if(rp->m == m)
-				break;
-			lrp = &rp->next;
-		}
-		if(rp == nil)
-			break;
+	qlock(priv);
+	if((rp = findreport(priv->reports, pr, ifc)) != nil){
+		Ipmulti *mp, **lmp;
 
-		/*
-		 *  if someone else has reported a group,
-		 *  we don't have to.
-		 */
 		lmp = &rp->multi;
 		for(mp = *lmp; mp; mp = *lmp){
-			if(mp->addr == group){
+			if(ipcmp(mp->ma, group) == 0){
 				*lmp = mp->next;
 				free(mp);
 				break;
@@ -258,40 +304,139 @@
 			}
 			lmp = &mp->next;
 		}
+	}
+	qunlock(priv);
+}
 
+static void
+mldiput(Proto *mld, Ipifc *ifc, Block *bp)
+{
+	MLDpkt *p;
+	uchar *opt, *payload;
+
+	p = (MLDpkt*)(bp->rp);
+	/* check we have the hop-by-hop header */
+	if((p->vcf[0] & 0xF0) != IP_VER6 || p->proto != IP_MLDPROTO)
+		goto error;
+	if(p->ttl != 1 || !isv6mcast(p->dst) || !islinklocal(p->src))
+		goto error;
+
+	/* strip the hop-by-hop option header */
+	if(BLEN(bp) < IP6HDR+sizeof(mldhbhopt))
+		goto error;
+	opt = bp->rp + IP6HDR;
+	if(opt[0] != ICMPv6)
+		goto error;
+	payload = opt + ((int)opt[1] + 1)*8;
+	if(payload >= bp->wp)
+		goto error;
+	if(memcmp(opt+2, mldhbhopt+2, 4) != 0)
+		goto error;
+	memmove(payload-IP6HDR, bp->rp, IP6HDR);
+	bp->rp = payload - IP6HDR;
+	if(BLEN(bp) < MLDPKTSZ)
+		goto error;
+	p = (MLDpkt*)bp->rp;
+
+	/* verify ICMPv6 checksum */
+	hnputl(p->vcf, 0);  	/* borrow IP header as pseudoheader */
+	p->ttl = ICMPv6;
+	p->proto = 0;
+	hnputs(p->ploadlen, MLDPKTSZ-IP6HDR);
+	if(ptclcsum(bp, 0, MLDPKTSZ))
+		goto error;
+
+	if(ipcmp(p->group, IPnoaddr) != 0 && ipismulticast(p->group) != V6)
+		goto error;
+
+	switch(p->type){
+	case MLDquery:
+		queuereport(mld, ifc, p->group, nhgets(p->delay)/MSPTICK);
 		break;
+	case MLDreport:
+		purgereport(mld, ifc, p->group);
+		break;
 	}
-	unlock(&igmpalloc);
+error:
+	freeblist(bp);
+}
 
+static void
+igmpiput(Proto *igmp, Ipifc *ifc, Block *bp)
+{
+	uchar group[IPaddrlen];
+	IGMPpkt *p;
+
+	p = (IGMPpkt*)bp->rp;
+	if((p->vihl & 0xF0) != IP_VER4)
+		goto error;
+	if(BLEN(bp) < IGMP_IPHDRSIZE+IGMP_HDRSIZE)
+		goto error;
+	if((p->vertype>>4) != 1)
+		goto error;
+	if(ptclcsum(bp, IGMP_IPHDRSIZE, IGMP_HDRSIZE))
+		goto error;
+
+	v4tov6(group, p->group);
+	if(ipcmp(group, v4prefix) != 0 && ipismulticast(group) != V4)
+		goto error;
+
+	switch(p->vertype & 0xF){
+	case IGMPquery:
+		queuereport(igmp, ifc, group, p->resptime);
+		break;
+	case IGMPreport:
+	case IGMPv2report:
+		purgereport(igmp, ifc, group);
+		break;
+	}
 error:
-	freeb(bp);
+	freeblist(bp);
 }
 
-int
-igmpstats(char *buf, int len)
+static void
+multicastreport(Fs *f, Ipifc *ifc, uchar *ma, uchar *ia, int done)
 {
-	return snprint(buf, len, "\trcvd %d %d\n\tsent %d %d\n",
-		stats.inqueries, stats.inreports,
-		stats.outqueries, stats.outreports);
+	Proto *pr = Fsrcvpcolx(f, isv4(ma)? IP_IGMPPROTO: IP_MLDPROTO);
+	purgereport(pr, ifc, ma);
+	sendreport(pr, ia, ma, done);
 }
 
 void
-igmpinit(Fs *fs)
+igmpinit(Fs *f)
 {
-	igmp.name = "igmp";
-	igmp.connect = nil;
-	igmp.announce = nil;
-	igmp.ctl = nil;
-	igmp.state = nil;
-	igmp.close = nil;
-	igmp.rcv = igmpiput;
-	igmp.stats = igmpstats;
-	igmp.ipproto = IP_IGMPPROTO;
-	igmp.nc = 0;
-	igmp.ptclsize = 0;
+	Proto *igmp, *mld;
 
-	igmpreportfn = igmpsendreport;
-	kproc("igmpproc", igmpproc, 0);
+	igmp = smalloc(sizeof(Proto));
+	igmp->priv = smalloc(sizeof(Priv));
+	igmp->name = "igmp";
+	igmp->connect = nil;
+	igmp->announce = nil;
+	igmp->ctl = nil;
+	igmp->state = nil;
+	igmp->close = nil;
+	igmp->rcv = igmpiput;
+	igmp->stats = nil;
+	igmp->ipproto = IP_IGMPPROTO;
+	igmp->nc = 0;
+	igmp->ptclsize = 0;
+	Fsproto(f, igmp);
 
-	Fsproto(fs, &igmp);
+	mld = smalloc(sizeof(Proto));
+	mld->priv = igmp->priv;
+	mld->name = "mld";
+	mld->connect = nil;
+	mld->announce = nil;
+	mld->ctl = nil;
+	mld->state = nil;
+	mld->close = nil;
+	mld->rcv = mldiput;
+	mld->stats = nil;
+	mld->ipproto = IP_MLDPROTO;
+	mld->nc = 0;
+	mld->ptclsize = 0;
+	Fsproto(f, mld);
+
+	multicastreportfn = multicastreport;
+	kproc("igmpproc", igmpproc, igmp);
 }
--- a/sys/src/9/ip/ip.h
+++ b/sys/src/9/ip/ip.h
@@ -725,6 +725,7 @@
 extern Iplifc*	iplocalonifc(Ipifc *ifc, uchar *ip);
 extern Iplifc*	ipremoteonifc(Ipifc *ifc, uchar *ip);
 extern int	ipproxyifc(Fs *f, Ipifc *ifc, uchar *ip);
+extern Ipmulti*	ipifcgetmulti(Fs *f, Ipifc *ifc, uchar *ma);
 extern void	ipifcremmulti(Conv *c, uchar *ma, uchar *ia);
 extern void	ipifcaddmulti(Conv *c, uchar *ma, uchar *ia);
 extern char*	ipifcrem(Ipifc *ifc, char **argv, int argc);
@@ -777,4 +778,4 @@
 /*
  *  global to all of the stack
  */
-extern void	(*igmpreportfn)(Ipifc*, uchar*);
+extern void	(*multicastreportfn)(Fs*, Ipifc*, uchar*, uchar*, int);
--- a/sys/src/9/ip/ipaux.c
+++ b/sys/src/9/ip/ipaux.c
@@ -196,8 +196,7 @@
 void
 ipv62smcast(uchar *smcast, uchar *a)
 {
-	assert(IPaddrlen == 16);
-	memmove(smcast, v6solicitednode, IPaddrlen);
+	ipmove(smcast, v6solicitednode);
 	smcast[13] = a[13];
 	smcast[14] = a[14];
 	smcast[15] = a[15];
--- a/sys/src/9/ip/ipifc.c
+++ b/sys/src/9/ip/ipifc.c
@@ -19,6 +19,7 @@
 };
 
 Medium *media[Maxmedia] = { 0 };
+void (*multicastreportfn)(Fs*, Ipifc*, uchar*, uchar*, int);
 
 /*
  *  cache of local addresses (addresses we answer to)
@@ -40,18 +41,6 @@
 	Ipself	*hash[NHASH];	/* hash chains */
 };
 
-/*
- *  Multicast addresses are chained onto a Chan so that
- *  we can remove them when the Chan is closed.
- */
-typedef struct Ipmcast Ipmcast;
-struct Ipmcast
-{
-	Ipmcast	*next;
-	uchar	ma[IPaddrlen];	/* multicast address */
-	uchar	ia[IPaddrlen];	/* interface address */
-};
-
 /* quick hash for ip addresses */
 #define hashipa(a) (((a)[IPaddrlen-2] + (a)[IPaddrlen-1])%NHASH)
 
@@ -611,6 +600,9 @@
 		addselfcache(f, ifc, lifc, bcast, Rbcast);
 
 		addselfcache(f, ifc, lifc, IPv4bcast, Rbcast);
+
+		/* add all nodes multicast address */
+		addselfcache(f, ifc, lifc, IPv4allsys, Rmulti);
 	} else {
 		if(ipcmp(ip, v6loopback) == 0) {
 			/* add node-local mcast address */
@@ -684,11 +676,11 @@
 
 	/* remove route for all nodes multicast */
 	if((lifc->type & Rv4) == 0){
-		if(ipcmp(lifc->local, v6loopback) == 0)
+		if(ipcmp(lifc->local, v6loopback) == 0){
 			remroute(f, v6allnodesN, v6allnodesNmask,
 				lifc->local, IPallbits,
 				v6allnodesN, Rmulti, ifc, tifc);
-
+		}
 		remroute(f, v6allnodesL, v6allnodesLmask,
 			lifc->local, IPallbits,
 			v6allnodesL, Rmulti, ifc, tifc);
@@ -960,8 +952,12 @@
 				IPallbits : IPnoaddr,
 			a, type, ifc, tifc);
 
-		if((type & Rmulti) && ifc->m->addmulti != nil)
-			(*ifc->m->addmulti)(ifc, a, lifc->local);
+		if(type & Rmulti){
+			if(ifc->m->addmulti != nil)
+				(*ifc->m->addmulti)(ifc, a, lifc->local);
+			if(multicastreportfn != nil)
+				(*multicastreportfn)(f, ifc, a, lifc->local, 0);
+		}
 	} else
 		lp->ref++;
 
@@ -1072,6 +1068,26 @@
 	if(--(link->ref) != 0)
 		goto out;
 
+	/* ref == 0, remove from both chains and free the link */
+	*l_lifc = link->lifclink;
+	*l_self = link->selflink;
+	iplinkfree(link);
+
+	if((p->type & Rmulti) != 0){
+		if(multicastreportfn != nil){
+			if(!waserror()){
+				(*multicastreportfn)(f, ifc, a, lifc->local, 1);
+				poperror();
+			}
+		}
+		if(ifc->m->remmulti != nil){
+			if(!waserror()){
+				(*ifc->m->remmulti)(ifc, a, lifc->local);
+				poperror();
+			}
+		}
+	}
+
 	/* remove from routing table */
 	remroute(f, a, IPallbits,
 		lifc->local, 
@@ -1079,18 +1095,6 @@
 			IPallbits : IPnoaddr,
 		a, p->type, ifc, tifc);
 
-	if((p->type & Rmulti) && ifc->m->remmulti != nil){
-		if(!waserror()){
-			(*ifc->m->remmulti)(ifc, a, lifc->local);
-			poperror();
-		}
-	}
-
-	/* ref == 0, remove from both chains and free the link */
-	*l_lifc = link->lifclink;
-	*l_self = link->selflink;
-	iplinkfree(link);
-
 	if(p->link != nil)
 		goto out;
 
@@ -1423,7 +1427,21 @@
 	return nil;
 }
 
+/*
+ *  return link-local interface
+ */
+Iplifc*
+iplinklocalifc(Ipifc *ifc)
+{
+	Iplifc *lifc;
 
+	for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next)
+		if(islinklocal(lifc->local))
+			return lifc;
+
+	return nil;
+}
+
 /*
  *  See if we're proxying for this address on this interface
  */
@@ -1441,6 +1459,65 @@
 }
 
 /*
+ *  Return a copy of the multicast addresses on the
+ *  specified interface and multicast address.
+ */
+Ipmulti*
+ipifcgetmulti(Fs *f, Ipifc *ifc, uchar *ma)
+{
+	Ipmulti *head, *tail, *mcast;
+	Iplifc *lifc;
+	Iplink *link;
+	Ipself *self;
+	int type;
+
+	type = Rmulti;
+	if(isv4(ma))
+		type |= Rv4;
+	if(ipismulticast(ma) == ((type & Rv4)? V4: V6)){
+		if(ipforme(f, ma) != Rmulti)
+			return nil;
+	} else {
+		ma = nil;
+	}
+
+	head = tail = nil;
+	for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next) {
+		if((type & Rv4) == 0 && !islinklocal(lifc->local))
+			continue;
+
+		for(link = lifc->link; link != nil; link = link->lifclink) {
+			self = link->self;
+			if((self->type & (Rv4|Rmulti)) != type)
+				continue;
+
+			if(ma != nil){
+				if(ipcmp(self->a, ma) != 0)
+					continue;
+			} else {
+				if(ipcmp(self->a, (type & Rv4)? IPv4allsys: v6allnodesL) == 0)
+					continue;
+			}
+
+			mcast = smalloc(sizeof(Ipmulti));
+			mcast->next = nil;
+			ipmove(mcast->ma, self->a);
+			ipmove(mcast->ia, lifc->local);
+
+			if(ma != nil)
+				return mcast;
+
+			if(head == nil)
+				head = mcast;
+			else
+				tail->next = mcast;
+			tail = mcast;
+		}
+	}
+	return head;
+}
+
+/*
  *  add a multicast address to an interface.
  */
 void
@@ -1451,6 +1528,8 @@
 	Ipifc *ifc;
 	Fs *f;
 
+	if(!ipismulticast(ma))
+		error("addmulti for a non multicast address");
 	if(isv4(ma) != isv4(ia))
 		error("incompatible multicast/interface ip address");
 
@@ -1467,6 +1546,11 @@
 		}
 		if((lifc = iplocalonifc(ifc, ia)) != nil)
 			addselfcache(f, ifc, lifc, ma, Rmulti);
+
+		/* for IPv6, must add also add to the link-local address */
+		if(!isv4(ia) && !islinklocal(ia) && (lifc = iplinklocalifc(ifc)) != nil)
+			addselfcache(f, ifc, lifc, ma, Rmulti);
+
 		runlock(ifc);
 		poperror();
 	}
@@ -1506,6 +1590,11 @@
 		rlock(ifc);
 		if(!waserror()){
 			if((lifc = iplocalonifc(ifc, ia)) != nil)
+				remselfcache(f, ifc, lifc, ma);
+			poperror();
+		}
+		if(!isv4(ia) && !islinklocal(ia) && !waserror()){
+			if((lifc = iplinklocalifc(ifc)) != nil)
 				remselfcache(f, ifc, lifc, ma);
 			poperror();
 		}
--- a/sys/src/9/ip/ipv6.h
+++ b/sys/src/9/ip/ipv6.h
@@ -78,7 +78,6 @@
 
 	/* various flags & constants */
 	v6MINTU		= 1280,
-	HOP_LIMIT	= 255,
 	IP6HDR		= 40,		/* sizeof(Ip6hdr) = 8 + 2*16 */
 	IP6FHDR		= 8, 		/* sizeof(Fraghdr6) */
 
--- a/sys/src/9/ip/netlog.c
+++ b/sys/src/9/ip/netlog.c
@@ -42,6 +42,7 @@
 	{ "il",		Logil, },
 	{ "tcp",	Logtcp, },
 	{ "icmp",	Logicmp, },
+	{ "igmp",	Logigmp, },
 	{ "udp",	Logudp, },
 	{ "compress",	Logcompress, },
 	{ "logilmsg",	Logilmsg, },
--- a/sys/src/9/ip/udp.c
+++ b/sys/src/9/ip/udp.c
@@ -100,6 +100,9 @@
 struct Udpcb
 {
 	uchar	headers;
+
+	/* source ip used for transmission */
+	uchar	srcip[IPaddrlen];
 };
 
 static char*
@@ -113,6 +116,7 @@
 	Fsconnected(c, e);
 	if(e != nil)
 		return e;
+	ipmove(((Udpcb*)c->ptcl)->srcip, c->laddr);
 	iphtadd(&upriv->ht, c);
 	return nil;
 }
@@ -139,6 +143,7 @@
 	if(e != nil)
 		return e;
 	Fsconnected(c, nil);
+	ipmove(((Udpcb*)c->ptcl)->srcip, c->laddr);
 	iphtadd(&upriv->ht, c);
 	return nil;
 }
@@ -170,6 +175,7 @@
 
 	ucb = (Udpcb*)c->ptcl;
 	ucb->headers = 0;
+	ipmove(ucb->srcip, IPnoaddr);
 }
 
 void
@@ -214,6 +220,7 @@
 		bp->rp += 2+2;			/* Ignore local port */
 		break;
 	default:
+		ipmove(laddr, IPnoaddr);
 		rport = 0;
 		break;
 	}
@@ -225,6 +232,7 @@
 			version = V6;
 	} else {
 		version = convipvers(c);
+		ipmove(laddr, ucb->srcip);
 	}
 
 	dlen = blocklen(bp);
@@ -243,16 +251,13 @@
 		if(ucb->headers) {
 			v6tov4(uh4->udpdst, raddr);
 			hnputs(uh4->udpdport, rport);
-			v6tov4(uh4->udpsrc, laddr);
 			rh = nil;
 		} else {
 			v6tov4(uh4->udpdst, c->raddr);
 			hnputs(uh4->udpdport, c->rport);
-			if(ipcmp(c->laddr, IPnoaddr) == 0)
-				findlocalip(f, c->laddr, c->raddr);
-			v6tov4(uh4->udpsrc, c->laddr);
 			rh = c;
 		}
+		v6tov4(uh4->udpsrc, laddr);
 		hnputs(uh4->udpsport, c->lport);
 		hnputs(uh4->udplen, ptcllen);
 		uh4->udpcksum[0] = 0;
@@ -279,16 +284,13 @@
 		if(ucb->headers) {
 			ipmove(uh6->udpdst, raddr);
 			hnputs(uh6->udpdport, rport);
-			ipmove(uh6->udpsrc, laddr);
 			rh = nil;
 		} else {
 			ipmove(uh6->udpdst, c->raddr);
 			hnputs(uh6->udpdport, c->rport);
-			if(ipcmp(c->laddr, IPnoaddr) == 0)
-				findlocalip(f, c->laddr, c->raddr);
-			ipmove(uh6->udpsrc, c->laddr);
 			rh = c;
 		}
+		ipmove(uh6->udpsrc, laddr);
 		hnputs(uh6->udpsport, c->lport);
 		hnputs(uh6->udplen, ptcllen);
 		uh6->udpcksum[0] = 0;
@@ -394,6 +396,7 @@
 	qlock(udp);
 	iph = iphtlook(&upriv->ht, raddr, rport, laddr, lport);
 	if(iph == nil){
+Noconv:
 		/* no conversation found */
 		upriv->ustats.udpNoPorts++;
 		qunlock(udp);
@@ -438,9 +441,23 @@
 
 	if(c->state == Announced){
 		if(ucb->headers == 0){
-			/* create a new conversation */
-			if(ipforme(f, laddr) != Runi)
-				ipv6local(ifc, laddr, 0, raddr);
+			uchar srcip[IPaddrlen];
+
+			switch(ipforme(f, laddr)){
+			default:
+				goto Noconv;
+			case Runi:
+				ipmove(srcip, laddr);
+				break;
+			case Rmulti:
+			case Rbcast:
+				/*
+				 * use the multicast address for reception,
+				 * and local unicast ip for transmission.
+				 */
+				ipv6local(ifc, srcip, 0, raddr);
+				break;
+			}
 			c = Fsnewcall(c, raddr, rport, laddr, lport, version);
 			if(c == nil){
 				qunlock(udp);
@@ -447,8 +464,9 @@
 				freeblist(bp);
 				return;
 			}
-			iphtadd(&upriv->ht, c);
 			ucb = (Udpcb*)c->ptcl;
+			ipmove(ucb->srcip, srcip);
+			iphtadd(&upriv->ht, c);
 		}
 	}