#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "ip.h"
enum
{
IGMP_IPHDRSIZE = 20, /* size of ip header */
IGMP_HDRSIZE = 8, /* size of IGMP header */
IP_IGMPPROTO = 2,
IGMPquery = 1,
IGMPreport = 2,
MSPTICK = 100,
MAXTIMEOUT = 10000/MSPTICK, /* at most 10 secs for a response */
};
typedef struct IGMPpkt IGMPpkt;
struct IGMPpkt
{
/* ip header */
byte vihl; /* Version and header length */
byte tos; /* Type of service */
byte len[2]; /* packet length (including headers) */
byte id[2]; /* Identification */
byte frag[2]; /* Fragment information */
byte Unused;
byte proto; /* Protocol */
byte cksum[2]; /* checksum of ip portion */
byte src[IPaddrlen]; /* Ip source */
byte dst[IPaddrlen]; /* Ip destination */
/* igmp header */
byte vertype; /* version and type */
byte unused;
byte igmpcksum[2]; /* checksum of igmp portion */
byte group[IPaddrlen]; /* multicast group */
};
/*
* lists for group reports
*/
typedef struct IGMPrep IGMPrep;
struct IGMPrep
{
IGMPrep *next;
Media *m;
int ticks;
Multicast *multi;
};
typedef struct IGMP IGMP;
struct IGMP
{
Lock;
Rendez r;
IGMPrep *reports;
};
IGMP igmpalloc;
Proto igmp;
extern Fs fs;
static struct Stats
{
ulong inqueries;
ulong outqueries;
ulong inreports;
ulong outreports;
} stats;
void
igmpsendreport(Media *m, byte *addr)
{
IGMPpkt *p;
Block *bp;
bp = allocb(sizeof(IGMPpkt));
if(bp == nil)
return;
p = (IGMPpkt*)bp->wp;
p->vihl = IP_VER4;
bp->wp += sizeof(IGMPpkt);
memset(bp->rp, 0, sizeof(IGMPpkt));
hnputl(p->src, Mediagetaddr(m));
hnputl(p->dst, Ipallsys);
p->vertype = (1<<4) | IGMPreport;
p->proto = IP_IGMPPROTO;
memmove(p->group, addr, IPaddrlen);
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 */
}
static int
isreport(void *a)
{
USED(a);
return igmpalloc.reports != 0;
}
void
igmpproc(void *a)
{
IGMPrep *rp, **lrp;
Multicast *mp, **lmp;
byte ip[IPaddrlen];
USED(a);
for(;;){
sleep(&igmpalloc.r, isreport, 0);
for(;;){
lock(&igmpalloc);
if(igmpalloc.reports == nil)
break;
/* look for a single report */
lrp = &igmpalloc.reports;
mp = nil;
for(rp = *lrp; rp; rp = *lrp){
rp->ticks++;
lmp = &rp->multi;
for(mp = *lmp; mp; mp = *lmp){
if(rp->ticks >= mp->timeout){
*lmp = mp->next;
break;
}
lmp = &mp->next;
}
if(mp != nil)
break;
if(rp->multi != nil){
lrp = &rp->next;
continue;
} else {
*lrp = rp->next;
free(rp);
}
}
unlock(&igmpalloc);
if(mp){
/* do a single report and try again */
hnputl(ip, mp->addr);
igmpsendreport(rp->m, ip);
free(mp);
continue;
}
tsleep(&up->sleep, return0, 0, MSPTICK);
}
unlock(&igmpalloc);
}
}
void
igmpiput(Media *m, Ipifc *, Block *bp)
{
int n;
IGMPpkt *ghp;
Ipaddr group;
IGMPrep *rp, **lrp;
Multicast *mp, **lmp;
ghp = (IGMPpkt*)(bp->rp);
netlog(Logigmp, "igmpiput: %d %I\n", ghp->vertype, ghp->group);
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;
}
group = nhgetl(ghp->group);
lock(&igmpalloc);
switch(ghp->vertype & 0xf){
case IGMPquery:
/*
* start reporting groups that we're a member of.
*/
stats.inqueries++;
for(rp = igmpalloc.reports; rp; rp = rp->next)
if(rp->m == m)
break;
if(rp != nil)
break; /* already reporting */
mp = Mediacopymulti(m);
if(mp == nil)
break;
rp = malloc(sizeof(*rp));
if(rp == nil)
break;
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(&igmpalloc.r);
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;
/*
* 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){
*lmp = mp->next;
free(mp);
break;
}
lmp = &mp->next;
}
break;
}
unlock(&igmpalloc);
error:
freeb(bp);
}
int
igmpstats(char *buf, int len)
{
return snprint(buf, len, "\trcvd %d %d\n\tsent %d %d\n",
stats.inqueries, stats.inreports,
stats.outqueries, stats.outreports);
}
void
igmpinit(Fs *fs)
{
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;
igmpreportfn = igmpsendreport;
kproc("igmpproc", igmpproc, 0);
Fsproto(fs, &igmp);
}
|