diff options
author | cinap_lenrek <cinap_lenrek@felloff.net> | 2022-11-20 15:38:36 +0000 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@felloff.net> | 2022-11-20 15:38:36 +0000 |
commit | 98a66671cf6900b6239ac1b48638ffa835e75c27 (patch) | |
tree | e2e11db47365f6187bcec0bf49fdb86d8790202e /sys/src/9/ip/igmp.c | |
parent | 0ed8a3bd7b1bbe2a3857f2abcaabdc4edd2d8b05 (diff) |
devip: lilu dallas multicast.
Allow accepting udp "connections" using a multicast local address.
Before, it was only possible to receive multicast using the headers
option. Having a connection orirented stream can be very usefull
when receiving multicast audio data. One gets a "connection" for
every source.
Implement (optional) support for IGMPv2 and MLDv1.
This can be usefull if bridges on the network have IGMP/MLD snooping
enabled, and wont forward multicast traffic unless we report what
we excpect. This is experimental for now, so the igmp protocol
must be manually added to the kernel configuration.
Diffstat (limited to 'sys/src/9/ip/igmp.c')
-rw-r--r-- | sys/src/9/ip/igmp.c | 481 |
1 files changed, 313 insertions, 168 deletions
diff --git a/sys/src/9/ip/igmp.c b/sys/src/9/ip/igmp.c index 9e4b86c79..6122b28e8 100644 --- a/sys/src/9/ip/igmp.c +++ b/sys/src/9/ip/igmp.c @@ -1,6 +1,5 @@ /* - * igmp - internet group management protocol - * unfinished. + * IGMPv2 - internet group management protocol (and MLDv1) */ #include "u.h" #include "../port/lib.h" @@ -10,6 +9,7 @@ #include "../port/error.h" #include "ip.h" +#include "ipv6.h" enum { @@ -19,6 +19,14 @@ enum 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 */ @@ -36,262 +44,399 @@ struct IGMPpkt uchar Unused; uchar proto; /* Protocol */ uchar cksum[2]; /* checksum of ip portion */ - uchar src[IPaddrlen]; /* Ip source */ - uchar dst[IPaddrlen]; /* Ip destination */ + uchar src[4]; /* Ip source */ + uchar dst[4]; /* Ip destination */ /* igmp header */ uchar vertype; /* version and type */ - uchar unused; - uchar igmpcksum[2]; /* checksum of igmp portion */ - uchar group[IPaddrlen]; /* multicast group */ + uchar resptime; + uchar igmpcksum[2]; /* checksum of igmp portion */ + uchar group[4]; /* multicast group */ uchar payload[]; }; #define IGMPPKTSZ offsetof(IGMPpkt, payload[0]) -/* - * lists for group reports - */ -typedef struct IGMPrep IGMPrep; -struct IGMPrep -{ - IGMPrep *next; - Medium *m; - int ticks; - Multicast *multi; -}; +typedef struct MLDpkt MLDpkt; +struct MLDpkt { + IPV6HDR; -typedef struct IGMP IGMP; -struct IGMP -{ - Lock; - Rendez r; - IGMPrep *reports; + uchar type; + uchar code; + uchar cksum[2]; + uchar delay[2]; + uchar res[2]; + uchar group[IPaddrlen]; + uchar payload[]; }; -IGMP igmpalloc; +#define MLDPKTSZ offsetof(MLDpkt, payload[0]) + +static uchar mldhbhopt[] = { + ICMPv6, /* NextHeader */ + 0x00, /* HeaderLength */ + 0x05, /* Option: Router Alert */ + 0x02, /* Length */ + 0x00, 0x00, /* MLD */ - Proto igmp; -extern Fs fs; + 0x01, /* Option: PadN */ + 0x00, /* Length */ +}; -static struct Stats +typedef struct Report Report; +struct Report { - ulong inqueries; - ulong outqueries; - ulong inreports; - ulong outreports; -} stats; + Report *next; + Proto *proto; + Ipifc *ifc; + int ifcid; + int timeout; /* in MSPTICK's */ + Ipmulti *multi; +}; -void -igmpsendreport(Medium *m, uchar *addr) +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(sizeof(IGMPpkt)); - p = (IGMPpkt*)bp->wp; - p->vihl = IP_VER4; + bp = allocb(IGMPPKTSZ); bp->wp += IGMPPKTSZ; - memset(bp->rp, 0, IGMPPKTSZ); - hnputl(p->src, Mediumgetaddr(m)); - hnputl(p->dst, Ipallsys); - p->vertype = (1<<4) | IGMPreport; + 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, addr, IPaddrlen); + memmove(p->group, group+IPv4off, IPv4addrlen); 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 */ + ipoput4(f, bp, 0, 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, 0, 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) { - USED(a); - return igmpalloc.reports != 0; + return ((Priv*)a)->reports != 0; } - -void +static void igmpproc(void *a) { - IGMPrep *rp, **lrp; - Multicast *mp, **lmp; - uchar ip[IPaddrlen]; - - USED(a); + Proto *pr, *igmp = a; + Priv *priv = igmp->priv; + Report *rp, **lrp; + Ipmulti *mp, **lmp; for(;;){ - sleep(&igmpalloc.r, isreport, 0); + sleep(&priv->r, isreport, priv); for(;;){ - lock(&igmpalloc); - - if(igmpalloc.reports == nil) + qlock(priv); + if(priv->reports == nil) break; /* look for a single report */ - lrp = &igmpalloc.reports; mp = nil; - for(rp = *lrp; rp; rp = *lrp){ - rp->ticks++; + pr = nil; + lrp = &priv->reports; + for(rp = *lrp; rp != nil; rp = *lrp){ lmp = &rp->multi; - for(mp = *lmp; mp; mp = *lmp){ - if(rp->ticks >= mp->timeout){ + for(mp = *lmp; mp != nil; mp = *lmp){ + if(rp->timeout <= 1 || nrand(rp->timeout) == 0){ *lmp = mp->next; break; } lmp = &mp->next; } - if(mp != nil) - break; - + pr = rp->proto; if(rp->multi != nil){ + rp->timeout--; lrp = &rp->next; - continue; } else { *lrp = rp->next; free(rp); } + if(mp != nil) + break; } - unlock(&igmpalloc); + qunlock(priv); - if(mp){ + if(mp != nil){ /* do a single report and try again */ - hnputl(ip, mp->addr); - igmpsendreport(rp->m, ip); + if(pr != nil && !waserror()){ + sendreport(pr, mp->ia, mp->ma, 0); + poperror(); + } free(mp); continue; } tsleep(&up->sleep, return0, 0, MSPTICK); } - unlock(&igmpalloc); + 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; } -void -igmpiput(Medium *m, Ipifc *, Block *bp) +static void +queuereport(Proto *pr, Ipifc *ifc, uchar *group, int timeout) { - 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; - } + Priv *priv = pr->priv; + Report *rp; - group = nhgetl(ghp->group); - - lock(&igmpalloc); - switch(ghp->vertype & 0xf){ - case IGMPquery: + qlock(priv); + if(findreport(priv->reports, pr, ifc) != nil){ /* - * start reporting groups that we're a member of. + * we are already reporting on this interface, + * wait for the report to time-out. */ - stats.inqueries++; - for(rp = igmpalloc.reports; rp; rp = rp->next) - if(rp->m == m) - break; - if(rp != nil) - break; /* already reporting */ - - mp = Mediumcopymulti(m); - if(mp == nil) - break; - - rp = malloc(sizeof(*rp)); - if(rp == nil) - break; + qunlock(priv); + return; + } - 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; + /* + * 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); +} - wakeup(&igmpalloc.r); +static void +purgereport(Proto *pr, Ipifc *ifc, uchar *group) +{ + Priv *priv = pr->priv; + Report *rp; - 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; + qlock(priv); + if((rp = findreport(priv->reports, pr, ifc)) != nil){ + Ipmulti *mp, **lmp; - /* - * 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){ + 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; } - unlock(&igmpalloc); +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: - freeb(bp); + freeblist(bp); } -int -igmpstats(char *buf, int len) +static void +multicastreport(Fs *f, Ipifc *ifc, uchar *ma, uchar *ia, int done) { - return snprint(buf, len, "\trcvd %d %d\n\tsent %d %d\n", - stats.inqueries, stats.inreports, - stats.outqueries, stats.outreports); + Proto *pr = Fsrcvpcolx(f, isv4(ma)? IP_IGMPPROTO: IP_MLDPROTO); + purgereport(pr, ifc, ma); + sendreport(pr, ia, ma, done); } void -igmpinit(Fs *fs) +igmpinit(Fs *f) { - 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); + 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); } |