code: plan9front

Download patch

ref: 9861f1a92b373a16939fd6f3c167e5dad9f253d3
parent: 7144ff2694ab898a8c19188a356ce6f03bb4de2d
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sat Apr 13 13:34:28 EDT 2024

igmp: maintain timeout per group per interface

We used to only allow a single report per interface,
ignoring queries if a interfce already had a report
in flight.

However, this is not correct. Imagine if there is
a query specific query for a group, we add the
report and then we will ignore all further
queries (general or specific) until that
report times out.

Instead, we should maintain the timeout (report)
for each group (and interface) individually.

This means, Report.multi must point to a *single*
Ipmulti. When we handle general queries,
we must create individual Reports for
each of our multicast addresses, but check if
such a report already exists (for that interface).

Because the individual check is basically
quadratic, organise the reports in a hash table
to make finding the existing reports per group
per interface cheaper.

--- a/sys/src/9/ip/igmp.c
+++ b/sys/src/9/ip/igmp.c
@@ -30,6 +30,9 @@
 
 	MSPTICK		= 100,
 	MAXTIMEOUT	= 10000/MSPTICK,	/* at most 10 secs for a response */
+
+	NHASH		= 1<<5,
+#define hashipa(a) (((a)[IPaddrlen-2] + (a)[IPaddrlen-1])%NHASH)
 };
 
 typedef struct IGMPpkt IGMPpkt;
@@ -100,7 +103,8 @@
 {
 	QLock;
 	Rendez	r;
-	Report	*reports;
+	int	nreports;
+	Report	*reports[NHASH];
 };
 
 static void
@@ -179,58 +183,53 @@
 static int
 isreport(void *a)
 {
-	return ((Priv*)a)->reports != 0;
+	return ((Priv*)a)->nreports != 0;
 }
 
 static void
 igmpproc(void *a)
 {
-	Proto *pr, *igmp = a;
+	Proto *igmp = a;
 	Priv *priv = igmp->priv;
-	Report *rp, **lrp;
-	Ipmulti *mp, **lmp;
+	Report *list, *rp, **lrp;
+	uint h;
 
 	for(;;){
 		sleep(&priv->r, isreport, priv);
 		for(;;){
 			qlock(priv);
-			if(priv->reports == nil)
+			if(priv->nreports == 0)
 				break;
 	
-			/* look for a single report */
-			mp = nil;
-			pr = nil;
-			lrp = &priv->reports;
-			for(rp = *lrp; rp != nil; rp = *lrp){
-				lmp = &rp->multi;
-				for(mp = *lmp; mp != nil; mp = *lmp){
-					if(rp->timeout <= 1 || nrand(rp->timeout) == 0){
-						*lmp = mp->next;
-						break;
+			/* time out reports and put them in a list */
+			list = nil;
+			for(h = 0; h < NHASH; h++){
+				lrp = &priv->reports[h];
+				while((rp = *lrp) != nil){
+					if(rp->timeout > 1 && nrand(rp->timeout) != 0){
+						lrp = &rp->next;
+						rp->timeout--;
+						continue;
 					}
-					lmp = &mp->next;
-				}
-				pr = rp->proto;
-				if(rp->multi != nil){
-					rp->timeout--;
-					lrp = &rp->next;
-				} else {
 					*lrp = rp->next;
-					free(rp);
+					rp->next = list;
+					list = rp;
+					priv->nreports--;
 				}
-				if(mp != nil)
-					break;
 			}
 			qunlock(priv);
 
-			if(mp != nil){
-				/* do a single report and try again */
-				if(pr != nil && !waserror()){
-					sendreport(pr, mp->ia, mp->ma, 0);
+			/* send all timed out reports */
+			while((rp = list) != nil){
+				list = rp->next;
+				rp->next = nil;
+
+				if(!waserror()){
+					sendreport(rp->proto, rp->multi->ia, rp->multi->ma, 0);
 					poperror();
 				}
-				free(mp);
-				continue;
+				free(rp->multi);
+				free(rp);
 			}
 
 			tsleep(&up->sleep, return0, 0, MSPTICK);
@@ -239,50 +238,58 @@
 	}
 }
 
-/*
- *  find report list for this protocol and interface
- */
-static Report*
-findreport(Report *rp, Proto *pr, Ipifc *ifc)
-{
-	for(; rp != nil; rp = rp->next)
-		if(rp->proto == pr && rp->ifc == ifc && rp->ifcid == ifc->ifcid)
-			return rp;
-
-	return nil;
-}
-
 static void
 queuereport(Proto *pr, Ipifc *ifc, uchar *group, int timeout)
 {
 	Priv *priv = pr->priv;
+	Ipmulti *mp, *xp;
 	Report *rp;
+	uint h;
 
-	qlock(priv);
-	if(findreport(priv->reports, pr, ifc) != nil){
-		/*
-		 *  we are already reporting on this interface,
-		 *  wait for the report to time-out.
-		 */
-		qunlock(priv);
-		return;
-	}
+	if(timeout < 1 || timeout > MAXTIMEOUT)
+		timeout = MAXTIMEOUT;
 
-	/*
-	 *  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);
+	for(mp = ipifcgetmulti(pr->f, ifc, group); mp != nil; mp = xp){
+		group = mp->ma;
+		xp = mp->next;
+		mp->next = nil;
 
-	rp->next = priv->reports;
-	priv->reports = rp;
+		h = hashipa(group);
+		qlock(priv);
+		for(rp = priv->reports[h]; rp != nil; rp = rp->next){
+			if(rp->proto == pr
+			&& rp->ifc == ifc && rp->ifcid == ifc->ifcid
+			&& ipcmp(rp->multi->ma, group) == 0)
+				break;
+		}
+		if(rp != nil){
+			/*
+			 * already reporting this group on this interface,
+			 * only update the timeout when it is shorter.
+			 */
+			if(timeout < rp->timeout)
+				rp->timeout = timeout;
+		Skip:
+			qunlock(priv);
+			free(mp);
+			continue;
+		}
+		rp = malloc(sizeof(Report));
+		if(rp == nil)
+			goto Skip;
 
-	wakeup(&priv->r);
-	qunlock(priv);
+		rp->proto = pr;
+		rp->ifc = ifc;
+		rp->ifcid = ifc->ifcid;
+		rp->timeout = timeout;
+		rp->multi = mp;
+
+		rp->next = priv->reports[h];
+		priv->reports[h] = rp;
+		if(priv->nreports++ == 0)
+			wakeup(&priv->r);
+		qunlock(priv);
+	}
 }
 
 static void
@@ -289,23 +296,29 @@
 purgereport(Proto *pr, Ipifc *ifc, uchar *group)
 {
 	Priv *priv = pr->priv;
-	Report *rp;
+	Report *rp, **lrp;
+	uint h;
 
+	h = hashipa(group);
 	qlock(priv);
-	if((rp = findreport(priv->reports, pr, ifc)) != nil){
-		Ipmulti *mp, **lmp;
+	for(lrp = &priv->reports[h]; (rp = *lrp) != nil; lrp = &rp->next){
+		if(rp->proto == pr
+		&& rp->ifc == ifc && rp->ifcid == ifc->ifcid
+		&& ipcmp(rp->multi->ma, group) == 0){
+			*lrp = rp->next;
+			rp->next = nil;
 
-		lmp = &rp->multi;
-		for(mp = *lmp; mp; mp = *lmp){
-			if(ipcmp(mp->ma, group) == 0){
-				*lmp = mp->next;
-				free(mp);
-				break;
-			}
-			lmp = &mp->next;
+			priv->nreports--;
+			break;
 		}
 	}
 	qunlock(priv);
+
+	if(rp == nil)
+		return;
+
+	free(rp->multi);
+	free(rp);
 }
 
 static void