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