ref: 8b056392cf24e906e4f2f490b88fa7d7670d3f20
dir: /sys/src/9/ip/igmp.c/
/*
* IGMPv2 - internet group management protocol (and MLDv1)
*/
#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
{
IGMP_IPHDRSIZE = 20, /* size of ip header */
IGMP_HDRSIZE = 8, /* size of IGMP header */
IP_IGMPPROTO = 2,
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 */
};
typedef struct IGMPpkt IGMPpkt;
struct IGMPpkt
{
/* ip header */
uchar vihl; /* Version and header length */
uchar tos; /* Type of service */
uchar len[2]; /* packet length (including headers) */
uchar id[2]; /* Identification */
uchar frag[2]; /* Fragment information */
uchar Unused;
uchar proto; /* Protocol */
uchar cksum[2]; /* checksum of ip portion */
uchar src[4]; /* Ip source */
uchar dst[4]; /* Ip destination */
/* igmp header */
uchar vertype; /* version and type */
uchar resptime;
uchar igmpcksum[2]; /* checksum of igmp portion */
uchar group[4]; /* multicast group */
uchar payload[];
};
#define IGMPPKTSZ offsetof(IGMPpkt, payload[0])
typedef struct MLDpkt MLDpkt;
struct MLDpkt {
IPV6HDR;
uchar type;
uchar code;
uchar cksum[2];
uchar delay[2];
uchar res[2];
uchar group[IPaddrlen];
uchar payload[];
};
#define MLDPKTSZ offsetof(MLDpkt, payload[0])
static uchar mldhbhopt[] = {
ICMPv6, /* NextHeader */
0x00, /* HeaderLength */
0x05, /* Option: Router Alert */
0x02, /* Length */
0x00, 0x00, /* MLD */
0x01, /* Option: PadN */
0x00, /* Length */
};
typedef struct Report Report;
struct Report
{
Report *next;
Proto *proto;
Ipifc *ifc;
int ifcid;
int timeout; /* in MSPTICK's */
Ipmulti *multi;
};
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(IGMPPKTSZ);
bp->wp += IGMPPKTSZ;
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, group+IPv4off, IPv4addrlen);
hnputs(p->igmpcksum, ptclcsum(bp, IGMP_IPHDRSIZE, IGMP_HDRSIZE));
ipoput4(f, bp, nil, 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, nil, 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)
{
return ((Priv*)a)->reports != 0;
}
static void
igmpproc(void *a)
{
Proto *pr, *igmp = a;
Priv *priv = igmp->priv;
Report *rp, **lrp;
Ipmulti *mp, **lmp;
for(;;){
sleep(&priv->r, isreport, priv);
for(;;){
qlock(priv);
if(priv->reports == nil)
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;
}
lmp = &mp->next;
}
pr = rp->proto;
if(rp->multi != nil){
rp->timeout--;
lrp = &rp->next;
} else {
*lrp = rp->next;
free(rp);
}
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);
poperror();
}
free(mp);
continue;
}
tsleep(&up->sleep, return0, 0, MSPTICK);
}
qunlock(priv);
}
}
/*
* 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;
Report *rp;
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;
}
/*
* 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->next = priv->reports;
priv->reports = rp;
wakeup(&priv->r);
qunlock(priv);
}
static void
purgereport(Proto *pr, Ipifc *ifc, uchar *group)
{
Priv *priv = pr->priv;
Report *rp;
qlock(priv);
if((rp = findreport(priv->reports, pr, ifc)) != nil){
Ipmulti *mp, **lmp;
lmp = &rp->multi;
for(mp = *lmp; mp; mp = *lmp){
if(ipcmp(mp->ma, group) == 0){
*lmp = mp->next;
free(mp);
break;
}
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;
}
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:
freeblist(bp);
}
static void
multicastreport(Fs *f, Ipifc *ifc, uchar *ma, uchar *ia, int done)
{
Proto *pr = Fsrcvpcolx(f, isv4(ma)? IP_IGMPPROTO: IP_MLDPROTO);
purgereport(pr, ifc, ma);
sendreport(pr, ia, ma, done);
}
void
igmpinit(Fs *f)
{
Proto *igmp, *mld;
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);
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);
}