git: 9front

Download patch

ref: 6b0cbf10c4a5297ca27a9f3a44cb444abae26f48
parent: e688bddb9d58a7ac42215e3c11c8041127fb4c87
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
--