X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=net%2Fipv6%2Fmcast.c;h=e3ec216958328ff12820d9f858a6041f847a758e;hb=refs%2Fheads%2Fvserver;hp=893fe40817ec1e39bb2d1f396a90f35e6b2ff0d2;hpb=9bf4aaab3e101692164d49b7ca357651eb691cb6;p=linux-2.6.git diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 893fe4081..e3ec21695 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -28,7 +28,6 @@ * - MLDv2 support */ -#include #include #include #include @@ -84,7 +83,7 @@ struct mld2_grec { __u8 grec_type; __u8 grec_auxwords; - __u16 grec_nsrcs; + __be16 grec_nsrcs; struct in6_addr grec_mca; struct in6_addr grec_src[0]; }; @@ -92,18 +91,18 @@ struct mld2_grec { struct mld2_report { __u8 type; __u8 resv1; - __u16 csum; - __u16 resv2; - __u16 ngrec; + __sum16 csum; + __be16 resv2; + __be16 ngrec; struct mld2_grec grec[0]; }; struct mld2_query { __u8 type; __u8 code; - __u16 csum; - __u16 mrc; - __u16 resv1; + __sum16 csum; + __be16 mrc; + __be16 resv1; struct in6_addr mca; #if defined(__LITTLE_ENDIAN_BITFIELD) __u8 qrv:3, @@ -117,17 +116,19 @@ struct mld2_query { #error "Please fix " #endif __u8 qqic; - __u16 nsrcs; + __be16 nsrcs; struct in6_addr srcs[0]; }; -struct in6_addr mld2_all_mcr = MLD2_ALL_MCR_INIT; +static struct in6_addr mld2_all_mcr = MLD2_ALL_MCR_INIT; /* Big mc list lock for all the sockets */ -static rwlock_t ipv6_sk_mc_lock = RW_LOCK_UNLOCKED; +static DEFINE_RWLOCK(ipv6_sk_mc_lock); static struct socket *igmp6_socket; +int __ipv6_dev_mc_dec(struct inet6_dev *idev, struct in6_addr *addr); + static void igmp6_join_group(struct ifmcaddr6 *ma); static void igmp6_leave_group(struct ifmcaddr6 *ma); static void igmp6_timer_handler(unsigned long data); @@ -141,12 +142,14 @@ static void mld_clear_delrec(struct inet6_dev *idev); static int sf_setstate(struct ifmcaddr6 *pmc); static void sf_markstate(struct ifmcaddr6 *pmc); static void ip6_mc_clear_src(struct ifmcaddr6 *pmc); -int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode, - int sfcount, struct in6_addr *psfsrc, int delta); -int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode, - int sfcount, struct in6_addr *psfsrc, int delta); -int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, - struct inet6_dev *idev); +static int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca, + int sfmode, int sfcount, struct in6_addr *psfsrc, + int delta); +static int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, + int sfmode, int sfcount, struct in6_addr *psfsrc, + int delta); +static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, + struct inet6_dev *idev); #define IGMP6_UNSOLICITED_IVAL (10*HZ) @@ -160,15 +163,15 @@ int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, #define MLDV2_MASK(value, nb) ((nb)>=32 ? (value) : ((1<<(nb))-1) & (value)) #define MLDV2_EXP(thresh, nbmant, nbexp, value) \ ((value) < (thresh) ? (value) : \ - ((MLDV2_MASK(value, nbmant) | (1<<(nbmant+nbexp))) << \ + ((MLDV2_MASK(value, nbmant) | (1<<(nbmant))) << \ (MLDV2_MASK((value) >> (nbmant), nbexp) + (nbexp)))) #define MLDV2_QQIC(value) MLDV2_EXP(0x80, 4, 3, value) #define MLDV2_MRC(value) MLDV2_EXP(0x8000, 12, 3, value) -#define IPV6_MLD_MAX_MSF 10 +#define IPV6_MLD_MAX_MSF 64 -int sysctl_mld_max_msf = IPV6_MLD_MAX_MSF; +int sysctl_mld_max_msf __read_mostly = IPV6_MLD_MAX_MSF; /* * socket join on multicast group @@ -184,6 +187,16 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex, struct in6_addr *addr) if (!ipv6_addr_is_multicast(addr)) return -EINVAL; + read_lock_bh(&ipv6_sk_mc_lock); + for (mc_lst=np->ipv6_mc_list; mc_lst; mc_lst=mc_lst->next) { + if ((ifindex == 0 || mc_lst->ifindex == ifindex) && + ipv6_addr_equal(&mc_lst->addr, addr)) { + read_unlock_bh(&ipv6_sk_mc_lock); + return -EADDRINUSE; + } + } + read_unlock_bh(&ipv6_sk_mc_lock); + mc_lst = sock_kmalloc(sk, sizeof(struct ipv6_mc_socklist), GFP_KERNEL); if (mc_lst == NULL) @@ -210,6 +223,7 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex, struct in6_addr *addr) mc_lst->ifindex = dev->ifindex; mc_lst->sfmode = MCAST_EXCLUDE; + rwlock_init(&mc_lst->sflock); mc_lst->sflist = NULL; /* @@ -245,7 +259,7 @@ int ipv6_sock_mc_drop(struct sock *sk, int ifindex, struct in6_addr *addr) write_lock_bh(&ipv6_sk_mc_lock); for (lnk = &np->ipv6_mc_list; (mc_lst = *lnk) !=NULL ; lnk = &mc_lst->next) { if ((ifindex == 0 || mc_lst->ifindex == ifindex) && - ipv6_addr_cmp(&mc_lst->addr, addr) == 0) { + ipv6_addr_equal(&mc_lst->addr, addr)) { struct net_device *dev; *lnk = mc_lst->next; @@ -254,23 +268,24 @@ int ipv6_sock_mc_drop(struct sock *sk, int ifindex, struct in6_addr *addr) if ((dev = dev_get_by_index(mc_lst->ifindex)) != NULL) { struct inet6_dev *idev = in6_dev_get(dev); + (void) ip6_mc_leave_src(sk, mc_lst, idev); if (idev) { - (void) ip6_mc_leave_src(sk,mc_lst,idev); + __ipv6_dev_mc_dec(idev, &mc_lst->addr); in6_dev_put(idev); } - ipv6_dev_mc_dec(dev, &mc_lst->addr); dev_put(dev); - } + } else + (void) ip6_mc_leave_src(sk, mc_lst, NULL); sock_kfree_s(sk, mc_lst, sizeof(*mc_lst)); return 0; } } write_unlock_bh(&ipv6_sk_mc_lock); - return -ENOENT; + return -EADDRNOTAVAIL; } -struct inet6_dev *ip6_mc_find_dev(struct in6_addr *group, int ifindex) +static struct inet6_dev *ip6_mc_find_dev(struct in6_addr *group, int ifindex) { struct net_device *dev = NULL; struct inet6_dev *idev = NULL; @@ -320,13 +335,14 @@ void ipv6_sock_mc_close(struct sock *sk) if (dev) { struct inet6_dev *idev = in6_dev_get(dev); + (void) ip6_mc_leave_src(sk, mc_lst, idev); if (idev) { - (void) ip6_mc_leave_src(sk, mc_lst, idev); + __ipv6_dev_mc_dec(idev, &mc_lst->addr); in6_dev_put(idev); } - ipv6_dev_mc_dec(dev, &mc_lst->addr); dev_put(dev); - } + } else + (void) ip6_mc_leave_src(sk, mc_lst, NULL); sock_kfree_s(sk, mc_lst, sizeof(*mc_lst)); @@ -345,6 +361,8 @@ int ip6_mc_source(int add, int omode, struct sock *sk, struct ipv6_pinfo *inet6 = inet6_sk(sk); struct ip6_sf_socklist *psl; int i, j, rv; + int leavegroup = 0; + int pmclocked = 0; int err; if (pgsr->gsr_group.ss_family != AF_INET6 || @@ -364,18 +382,23 @@ int ip6_mc_source(int add, int omode, struct sock *sk, err = -EADDRNOTAVAIL; + read_lock_bh(&ipv6_sk_mc_lock); for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) { if (pgsr->gsr_interface && pmc->ifindex != pgsr->gsr_interface) continue; - if (ipv6_addr_cmp(&pmc->addr, group) == 0) + if (ipv6_addr_equal(&pmc->addr, group)) break; } - if (!pmc) /* must have a prior join */ + if (!pmc) { /* must have a prior join */ + err = -EINVAL; goto done; + } /* if a source filter was set, must be the same mode as before */ if (pmc->sflist) { - if (pmc->sfmode != omode) + if (pmc->sfmode != omode) { + err = -EINVAL; goto done; + } } else if (pmc->sfmode != omode) { /* allow mode switches for empty-set filters */ ip6_mc_add_src(idev, group, omode, 0, NULL, 0); @@ -383,19 +406,28 @@ int ip6_mc_source(int add, int omode, struct sock *sk, pmc->sfmode = omode; } + write_lock_bh(&pmc->sflock); + pmclocked = 1; + psl = pmc->sflist; if (!add) { if (!psl) - goto done; + goto done; /* err = -EADDRNOTAVAIL */ rv = !0; for (i=0; isl_count; i++) { - rv = memcmp(&psl->sl_addr, group, + rv = memcmp(&psl->sl_addr[i], source, sizeof(struct in6_addr)); - if (rv >= 0) + if (rv == 0) break; } - if (!rv) /* source not found */ + if (rv) /* source not found */ + goto done; /* err = -EADDRNOTAVAIL */ + + /* special case - (INCLUDE, empty) == LEAVE_GROUP */ + if (psl->sl_count == 1 && omode == MCAST_INCLUDE) { + leavegroup = 1; goto done; + } /* update the interface filter */ ip6_mc_del_src(idev, group, omode, 1, source, 1); @@ -418,8 +450,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk, if (psl) count += psl->sl_max; - newpsl = (struct ip6_sf_socklist *)sock_kmalloc(sk, - IP6_SFLSIZE(count), GFP_ATOMIC); + newpsl = sock_kmalloc(sk, IP6_SFLSIZE(count), GFP_ATOMIC); if (!newpsl) { err = -ENOBUFS; goto done; @@ -435,8 +466,8 @@ int ip6_mc_source(int add, int omode, struct sock *sk, } rv = 1; /* > 0 for insert logic below if sl_count is 0 */ for (i=0; isl_count; i++) { - rv = memcmp(&psl->sl_addr, group, sizeof(struct in6_addr)); - if (rv >= 0) + rv = memcmp(&psl->sl_addr[i], source, sizeof(struct in6_addr)); + if (rv == 0) break; } if (rv == 0) /* address already there is an error */ @@ -449,9 +480,14 @@ int ip6_mc_source(int add, int omode, struct sock *sk, /* update the interface list */ ip6_mc_add_src(idev, group, omode, 1, source, 1); done: + if (pmclocked) + write_unlock_bh(&pmc->sflock); + read_unlock_bh(&ipv6_sk_mc_lock); read_unlock_bh(&idev->lock); in6_dev_put(idev); dev_put(dev); + if (leavegroup) + return ipv6_sock_mc_drop(sk, pgsr->gsr_interface, group); return err; } @@ -463,6 +499,7 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf) struct inet6_dev *idev; struct ipv6_pinfo *inet6 = inet6_sk(sk); struct ip6_sf_socklist *newpsl, *psl; + int leavegroup = 0; int i, err; group = &((struct sockaddr_in6 *)&gsf->gf_group)->sin6_addr; @@ -478,19 +515,28 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf) if (!idev) return -ENODEV; dev = idev->dev; - err = -EADDRNOTAVAIL; + + err = 0; + read_lock_bh(&ipv6_sk_mc_lock); + + if (gsf->gf_fmode == MCAST_INCLUDE && gsf->gf_numsrc == 0) { + leavegroup = 1; + goto done; + } for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) { if (pmc->ifindex != gsf->gf_interface) continue; - if (ipv6_addr_cmp(&pmc->addr, group) == 0) + if (ipv6_addr_equal(&pmc->addr, group)) break; } - if (!pmc) /* must have a prior join */ + if (!pmc) { /* must have a prior join */ + err = -EINVAL; goto done; + } if (gsf->gf_numsrc) { - newpsl = (struct ip6_sf_socklist *)sock_kmalloc(sk, - IP6_SFLSIZE(gsf->gf_numsrc), GFP_ATOMIC); + newpsl = sock_kmalloc(sk, IP6_SFLSIZE(gsf->gf_numsrc), + GFP_ATOMIC); if (!newpsl) { err = -ENOBUFS; goto done; @@ -508,8 +554,12 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf) sock_kfree_s(sk, newpsl, IP6_SFLSIZE(newpsl->sl_max)); goto done; } - } else + } else { newpsl = NULL; + (void) ip6_mc_add_src(idev, group, gsf->gf_fmode, 0, NULL, 0); + } + + write_lock_bh(&pmc->sflock); psl = pmc->sflist; if (psl) { (void) ip6_mc_del_src(idev, group, pmc->sfmode, @@ -519,10 +569,15 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf) (void) ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0); pmc->sflist = newpsl; pmc->sfmode = gsf->gf_fmode; + write_unlock_bh(&pmc->sflock); + err = 0; done: + read_unlock_bh(&ipv6_sk_mc_lock); read_unlock_bh(&idev->lock); in6_dev_put(idev); dev_put(dev); + if (leavegroup) + err = ipv6_sock_mc_drop(sk, gsf->gf_interface, group); return err; } @@ -550,11 +605,16 @@ int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf, dev = idev->dev; err = -EADDRNOTAVAIL; + /* + * changes to the ipv6_mc_list require the socket lock and + * a read lock on ip6_sk_mc_lock. We have the socket lock, + * so reading the list is safe. + */ for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) { if (pmc->ifindex != gsf->gf_interface) continue; - if (ipv6_addr_cmp(group, &pmc->addr) == 0) + if (ipv6_addr_equal(group, &pmc->addr)) break; } if (!pmc) /* must have a prior join */ @@ -572,6 +632,10 @@ int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf, copy_to_user(optval, gsf, GROUP_FILTER_SIZE(0))) { return -EFAULT; } + /* changes to psl require the socket lock, a read lock on + * on ipv6_sk_mc_lock and a write lock on pmc->sflock. We + * have the socket lock, so reading here is safe. + */ for (i=0; iipv6_mc_list; mc; mc = mc->next) { - if (ipv6_addr_cmp(&mc->addr, mc_addr) == 0) + if (ipv6_addr_equal(&mc->addr, mc_addr)) break; } if (!mc) { read_unlock(&ipv6_sk_mc_lock); return 1; } + read_lock(&mc->sflock); psl = mc->sflist; if (!psl) { rv = mc->sfmode == MCAST_EXCLUDE; @@ -615,7 +680,7 @@ int inet6_mc_check(struct sock *sk, struct in6_addr *mc_addr, int i; for (i=0; isl_count; i++) { - if (ipv6_addr_cmp(&psl->sl_addr[i], src_addr) == 0) + if (ipv6_addr_equal(&psl->sl_addr[i], src_addr)) break; } if (mc->sfmode == MCAST_INCLUDE && i >= psl->sl_count) @@ -623,6 +688,7 @@ int inet6_mc_check(struct sock *sk, struct in6_addr *mc_addr, if (mc->sfmode == MCAST_EXCLUDE && i < psl->sl_count) rv = 0; } + read_unlock(&mc->sflock); read_unlock(&ipv6_sk_mc_lock); return rv; @@ -702,12 +768,12 @@ static void mld_add_delrec(struct inet6_dev *idev, struct ifmcaddr6 *im) * for deleted items allows change reports to use common code with * non-deleted or query-response MCA's. */ - pmc = (struct ifmcaddr6 *)kmalloc(sizeof(*pmc), GFP_ATOMIC); + pmc = kzalloc(sizeof(*pmc), GFP_ATOMIC); if (!pmc) return; - memset(pmc, 0, sizeof(*pmc)); + spin_lock_bh(&im->mca_lock); - pmc->mca_lock = SPIN_LOCK_UNLOCKED; + spin_lock_init(&pmc->mca_lock); pmc->idev = im->idev; in6_dev_hold(idev); pmc->mca_addr = im->mca_addr; @@ -738,7 +804,7 @@ static void mld_del_delrec(struct inet6_dev *idev, struct in6_addr *pmca) write_lock_bh(&idev->mc_lock); pmc_prev = NULL; for (pmc=idev->mc_tomb; pmc; pmc=pmc->next) { - if (ipv6_addr_cmp(&pmc->mca_addr, pmca) == 0) + if (ipv6_addr_equal(&pmc->mca_addr, pmca)) break; pmc_prev = pmc; } @@ -814,7 +880,7 @@ int ipv6_dev_mc_inc(struct net_device *dev, struct in6_addr *addr) } for (mc = idev->mc_list; mc; mc = mc->next) { - if (ipv6_addr_cmp(&mc->mca_addr, addr) == 0) { + if (ipv6_addr_equal(&mc->mca_addr, addr)) { mc->mca_users++; write_unlock_bh(&idev->lock); ip6_mc_add_src(idev, &mc->mca_addr, MCAST_EXCLUDE, 0, @@ -828,7 +894,7 @@ int ipv6_dev_mc_inc(struct net_device *dev, struct in6_addr *addr) * not found: create a new one. */ - mc = kmalloc(sizeof(struct ifmcaddr6), GFP_ATOMIC); + mc = kzalloc(sizeof(struct ifmcaddr6), GFP_ATOMIC); if (mc == NULL) { write_unlock_bh(&idev->lock); @@ -836,7 +902,6 @@ int ipv6_dev_mc_inc(struct net_device *dev, struct in6_addr *addr) return -ENOMEM; } - memset(mc, 0, sizeof(struct ifmcaddr6)); init_timer(&mc->mca_timer); mc->mca_timer.function = igmp6_timer_handler; mc->mca_timer.data = (unsigned long) mc; @@ -847,7 +912,7 @@ int ipv6_dev_mc_inc(struct net_device *dev, struct in6_addr *addr) /* mca_stamp should be updated upon changes */ mc->mca_cstamp = mc->mca_tstamp = jiffies; atomic_set(&mc->mca_refcnt, 2); - mc->mca_lock = SPIN_LOCK_UNLOCKED; + spin_lock_init(&mc->mca_lock); /* initial mode is (EX, empty) */ mc->mca_sfmode = MCAST_EXCLUDE; @@ -870,13 +935,13 @@ int ipv6_dev_mc_inc(struct net_device *dev, struct in6_addr *addr) /* * device multicast group del */ -static int __ipv6_dev_mc_dec(struct net_device *dev, struct inet6_dev *idev, struct in6_addr *addr) +int __ipv6_dev_mc_dec(struct inet6_dev *idev, struct in6_addr *addr) { struct ifmcaddr6 *ma, **map; write_lock_bh(&idev->lock); for (map = &idev->mc_list; (ma=*map) != NULL; map = &ma->next) { - if (ipv6_addr_cmp(&ma->mca_addr, addr) == 0) { + if (ipv6_addr_equal(&ma->mca_addr, addr)) { if (--ma->mca_users == 0) { *map = ma->next; write_unlock_bh(&idev->lock); @@ -903,7 +968,7 @@ int ipv6_dev_mc_dec(struct net_device *dev, struct in6_addr *addr) if (!idev) return -ENODEV; - err = __ipv6_dev_mc_dec(dev, idev, addr); + err = __ipv6_dev_mc_dec(idev, addr); in6_dev_put(idev); @@ -951,7 +1016,7 @@ int ipv6_chk_mcast_addr(struct net_device *dev, struct in6_addr *group, if (idev) { read_lock_bh(&idev->lock); for (mc = idev->mc_list; mc; mc=mc->next) { - if (ipv6_addr_cmp(&mc->mca_addr, group) == 0) + if (ipv6_addr_equal(&mc->mca_addr, group)) break; } if (mc) { @@ -960,8 +1025,7 @@ int ipv6_chk_mcast_addr(struct net_device *dev, struct in6_addr *group, spin_lock_bh(&mc->mca_lock); for (psf=mc->mca_sources;psf;psf=psf->sf_next) { - if (ipv6_addr_cmp(&psf->sf_addr, - src_addr) == 0) + if (ipv6_addr_equal(&psf->sf_addr, src_addr)) break; } if (psf) @@ -1027,28 +1091,69 @@ static void igmp6_group_queried(struct ifmcaddr6 *ma, unsigned long resptime) ma->mca_flags |= MAF_TIMER_RUNNING; } -static void mld_marksources(struct ifmcaddr6 *pmc, int nsrcs, +/* mark EXCLUDE-mode sources */ +static int mld_xmarksources(struct ifmcaddr6 *pmc, int nsrcs, + struct in6_addr *srcs) +{ + struct ip6_sf_list *psf; + int i, scount; + + scount = 0; + for (psf=pmc->mca_sources; psf; psf=psf->sf_next) { + if (scount == nsrcs) + break; + for (i=0; imca_sfcount[MCAST_INCLUDE] || + pmc->mca_sfcount[MCAST_EXCLUDE] != + psf->sf_count[MCAST_EXCLUDE]) + continue; + if (ipv6_addr_equal(&srcs[i], &psf->sf_addr)) { + scount++; + break; + } + } + } + pmc->mca_flags &= ~MAF_GSQUERY; + if (scount == nsrcs) /* all sources excluded */ + return 0; + return 1; +} + +static int mld_marksources(struct ifmcaddr6 *pmc, int nsrcs, struct in6_addr *srcs) { struct ip6_sf_list *psf; int i, scount; + if (pmc->mca_sfmode == MCAST_EXCLUDE) + return mld_xmarksources(pmc, nsrcs, srcs); + + /* mark INCLUDE-mode sources */ + scount = 0; for (psf=pmc->mca_sources; psf; psf=psf->sf_next) { if (scount == nsrcs) break; - for (i=0; isf_addr) == 0) { + for (i=0; isf_addr)) { psf->sf_gsresp = 1; scount++; break; } + } + } + if (!scount) { + pmc->mca_flags &= ~MAF_GSQUERY; + return 0; } + pmc->mca_flags |= MAF_GSQUERY; + return 1; } int igmp6_event_query(struct sk_buff *skb) { - struct mld2_query *mlh2 = (struct mld2_query *) skb->h.raw; + struct mld2_query *mlh2 = NULL; struct ifmcaddr6 *ma; struct in6_addr *group; unsigned long max_delay; @@ -1101,6 +1206,13 @@ int igmp6_event_query(struct sk_buff *skb) /* clear deleted report items */ mld_clear_delrec(idev); } else if (len >= 28) { + int srcs_offset = sizeof(struct mld2_query) - + sizeof(struct icmp6hdr); + if (!pskb_may_pull(skb, srcs_offset)) { + in6_dev_put(idev); + return -EINVAL; + } + mlh2 = (struct mld2_query *) skb->h.raw; max_delay = (MLDV2_MRC(ntohs(mlh2->mrc))*HZ)/1000; if (!max_delay) max_delay = 1; @@ -1117,7 +1229,15 @@ int igmp6_event_query(struct sk_buff *skb) return 0; } /* mark sources to include, if group & source-specific */ - mark = mlh2->nsrcs != 0; + if (mlh2->nsrcs != 0) { + if (!pskb_may_pull(skb, srcs_offset + + ntohs(mlh2->nsrcs) * sizeof(struct in6_addr))) { + in6_dev_put(idev); + return -EINVAL; + } + mlh2 = (struct mld2_query *) skb->h.raw; + mark = 1; + } } else { in6_dev_put(idev); return -EINVAL; @@ -1132,8 +1252,7 @@ int igmp6_event_query(struct sk_buff *skb) } } else { for (ma = idev->mc_list; ma; ma=ma->next) { - if (group_type != IPV6_ADDR_ANY && - ipv6_addr_cmp(group, &ma->mca_addr) != 0) + if (!ipv6_addr_equal(group, &ma->mca_addr)) continue; spin_lock_bh(&ma->mca_lock); if (ma->mca_flags & MAF_TIMER_RUNNING) { @@ -1147,13 +1266,11 @@ int igmp6_event_query(struct sk_buff *skb) else ma->mca_flags &= ~MAF_GSQUERY; } - if (ma->mca_flags & MAF_GSQUERY) - mld_marksources(ma, ntohs(mlh2->nsrcs), - mlh2->srcs); - igmp6_group_queried(ma, max_delay); + if (!(ma->mca_flags & MAF_GSQUERY) || + mld_marksources(ma, ntohs(mlh2->nsrcs), mlh2->srcs)) + igmp6_group_queried(ma, max_delay); spin_unlock_bh(&ma->mca_lock); - if (group_type != IPV6_ADDR_ANY) - break; + break; } } read_unlock_bh(&idev->lock); @@ -1175,6 +1292,11 @@ int igmp6_event_report(struct sk_buff *skb) if (skb->pkt_type == PACKET_LOOPBACK) return 0; + /* send our report if the MC router may not have heard this report */ + if (skb->pkt_type != PACKET_MULTICAST && + skb->pkt_type != PACKET_BROADCAST) + return 0; + if (!pskb_may_pull(skb, sizeof(struct in6_addr))) return -EINVAL; @@ -1198,7 +1320,7 @@ int igmp6_event_report(struct sk_buff *skb) read_lock_bh(&idev->lock); for (ma = idev->mc_list; ma; ma=ma->next) { - if (ipv6_addr_cmp(&ma->mca_addr, addrp) == 0) { + if (ipv6_addr_equal(&ma->mca_addr, addrp)) { spin_lock(&ma->mca_lock); if (del_timer(&ma->mca_timer)) atomic_dec(&ma->mca_refcnt); @@ -1220,7 +1342,18 @@ static int is_in(struct ifmcaddr6 *pmc, struct ip6_sf_list *psf, int type, case MLD2_MODE_IS_EXCLUDE: if (gdeleted || sdeleted) return 0; - return !((pmc->mca_flags & MAF_GSQUERY) && !psf->sf_gsresp); + if (!((pmc->mca_flags & MAF_GSQUERY) && !psf->sf_gsresp)) { + if (pmc->mca_sfmode == MCAST_INCLUDE) + return 1; + /* don't include if this source is excluded + * in all filters + */ + if (psf->sf_count[MCAST_INCLUDE]) + return type == MLD2_MODE_IS_INCLUDE; + return pmc->mca_sfcount[MCAST_EXCLUDE] == + psf->sf_count[MCAST_EXCLUDE]; + } + return 0; case MLD2_CHANGE_TO_INCLUDE: if (gdeleted || sdeleted) return 0; @@ -1277,15 +1410,6 @@ static struct sk_buff *mld_newpack(struct net_device *dev, int size) return NULL; skb_reserve(skb, LL_RESERVED_SPACE(dev)); - if (dev->hard_header) { - unsigned char ha[MAX_ADDR_LEN]; - - ndisc_mc_map(&mld2_all_mcr, ha, dev, 1); - if (dev->hard_header(skb, dev, ETH_P_IPV6,ha,NULL,size) < 0) { - kfree_skb(skb); - return NULL; - } - } if (ipv6_get_lladdr(dev, &addr_buf)) { /* : @@ -1309,6 +1433,30 @@ static struct sk_buff *mld_newpack(struct net_device *dev, int size) return skb; } +static inline int mld_dev_queue_xmit2(struct sk_buff *skb) +{ + struct net_device *dev = skb->dev; + + if (dev->hard_header) { + unsigned char ha[MAX_ADDR_LEN]; + int err; + + ndisc_mc_map(&skb->nh.ipv6h->daddr, ha, dev, 1); + err = dev->hard_header(skb, dev, ETH_P_IPV6, ha, NULL, skb->len); + if (err < 0) { + kfree_skb(skb); + return err; + } + } + return dev_queue_xmit(skb); +} + +static inline int mld_dev_queue_xmit(struct sk_buff *skb) +{ + return NF_HOOK(PF_INET6, NF_IP6_POST_ROUTING, skb, NULL, skb->dev, + mld_dev_queue_xmit2); +} + static void mld_sendpack(struct sk_buff *skb) { struct ipv6hdr *pip6 = skb->nh.ipv6h; @@ -1317,7 +1465,7 @@ static void mld_sendpack(struct sk_buff *skb) struct inet6_dev *idev = in6_dev_get(skb->dev); int err; - IP6_INC_STATS(IPSTATS_MIB_OUTREQUESTS); + IP6_INC_STATS(idev, IPSTATS_MIB_OUTREQUESTS); payload_len = skb->tail - (unsigned char *)skb->nh.ipv6h - sizeof(struct ipv6hdr); mldlen = skb->tail - skb->h.raw; @@ -1326,12 +1474,12 @@ static void mld_sendpack(struct sk_buff *skb) pmr->csum = csum_ipv6_magic(&pip6->saddr, &pip6->daddr, mldlen, IPPROTO_ICMPV6, csum_partial(skb->h.raw, mldlen, 0)); err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, skb->dev, - dev_queue_xmit); + mld_dev_queue_xmit); if (!err) { ICMP6_INC_STATS(idev,ICMP6_MIB_OUTMSGS); - IP6_INC_STATS(IPSTATS_MIB_OUTMCASTPKTS); + IP6_INC_STATS(idev, IPSTATS_MIB_OUTMCASTPKTS); } else - IP6_INC_STATS(IPSTATS_MIB_OUTDISCARDS); + IP6_INC_STATS(idev, IPSTATS_MIB_OUTDISCARDS); if (likely(idev != NULL)) in6_dev_put(idev); @@ -1339,7 +1487,7 @@ static void mld_sendpack(struct sk_buff *skb) static int grec_size(struct ifmcaddr6 *pmc, int type, int gdel, int sdel) { - return sizeof(struct mld2_grec) + 4*mld_scount(pmc,type,gdel,sdel); + return sizeof(struct mld2_grec) + 16 * mld_scount(pmc,type,gdel,sdel); } static struct sk_buff *add_grhead(struct sk_buff *skb, struct ifmcaddr6 *pmc, @@ -1374,7 +1522,7 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc, struct mld2_report *pmr; struct mld2_grec *pgr = NULL; struct ip6_sf_list *psf, *psf_next, *psf_prev, **psf_list; - int scount, first, isquery, truncate; + int scount, stotal, first, isquery, truncate; if (pmc->mca_flags & MAF_NOREPORT) return skb; @@ -1384,25 +1532,13 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc, truncate = type == MLD2_MODE_IS_EXCLUDE || type == MLD2_CHANGE_TO_EXCLUDE; + stotal = scount = 0; + psf_list = sdeleted ? &pmc->mca_tomb : &pmc->mca_sources; - if (!*psf_list) { - if (type == MLD2_ALLOW_NEW_SOURCES || - type == MLD2_BLOCK_OLD_SOURCES) - return skb; - if (pmc->mca_crcount || isquery) { - /* make sure we have room for group header and at - * least one source. - */ - if (skb && AVAILABLE(skb) < sizeof(struct mld2_grec)+ - sizeof(struct in6_addr)) { - mld_sendpack(skb); - skb = NULL; /* add_grhead will get a new one */ - } - skb = add_grhead(skb, pmc, type, &pgr); - } - return skb; - } + if (!*psf_list) + goto empty_source; + pmr = skb ? (struct mld2_report *)skb->h.raw : NULL; /* EX and TO_EX get a fresh packet, if needed */ @@ -1415,7 +1551,6 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc, } } first = 1; - scount = 0; psf_prev = NULL; for (psf=*psf_list; psf; psf=psf_next) { struct in6_addr *psrc; @@ -1447,9 +1582,11 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc, skb = add_grhead(skb, pmc, type, &pgr); first = 0; } + if (!skb) + return NULL; psrc = (struct in6_addr *)skb_put(skb, sizeof(*psrc)); *psrc = psf->sf_addr; - scount++; + scount++; stotal++; if ((type == MLD2_ALLOW_NEW_SOURCES || type == MLD2_BLOCK_OLD_SOURCES) && psf->sf_crcount) { psf->sf_crcount--; @@ -1464,6 +1601,21 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc, } psf_prev = psf; } + +empty_source: + if (!stotal) { + if (type == MLD2_ALLOW_NEW_SOURCES || + type == MLD2_BLOCK_OLD_SOURCES) + return skb; + if (pmc->mca_crcount || isquery) { + /* make sure we have room for group header */ + if (skb && AVAILABLE(skb) < sizeof(struct mld2_grec)) { + mld_sendpack(skb); + skb = NULL; /* add_grhead will get a new one */ + } + skb = add_grhead(skb, pmc, type, &pgr); + } + } if (pgr) pgr->grec_nsrcs = htons(scount); @@ -1545,11 +1697,11 @@ static void mld_send_cr(struct inet6_dev *idev) skb = add_grec(skb, pmc, dtype, 1, 1); } if (pmc->mca_crcount) { - pmc->mca_crcount--; if (pmc->mca_sfmode == MCAST_EXCLUDE) { type = MLD2_CHANGE_TO_INCLUDE; skb = add_grec(skb, pmc, type, 1, 0); } + pmc->mca_crcount--; if (pmc->mca_crcount == 0) { mld_clear_zeros(&pmc->mca_tomb); mld_clear_zeros(&pmc->mca_sources); @@ -1583,12 +1735,12 @@ static void mld_send_cr(struct inet6_dev *idev) /* filter mode changes */ if (pmc->mca_crcount) { - pmc->mca_crcount--; if (pmc->mca_sfmode == MCAST_EXCLUDE) type = MLD2_CHANGE_TO_EXCLUDE; else type = MLD2_CHANGE_TO_INCLUDE; skb = add_grec(skb, pmc, type, 0, 0); + pmc->mca_crcount--; } spin_unlock_bh(&pmc->mca_lock); } @@ -1613,7 +1765,10 @@ static void igmp6_send(struct in6_addr *addr, struct net_device *dev, int type) IPV6_TLV_ROUTERALERT, 2, 0, 0, IPV6_TLV_PADN, 0 }; - IP6_INC_STATS(IPSTATS_MIB_OUTREQUESTS); + rcu_read_lock(); + IP6_INC_STATS(__in6_dev_get(dev), + IPSTATS_MIB_OUTREQUESTS); + rcu_read_unlock(); snd_addr = addr; if (type == ICMPV6_MGM_REDUCTION) { snd_addr = &all_routers; @@ -1627,17 +1782,14 @@ static void igmp6_send(struct in6_addr *addr, struct net_device *dev, int type) skb = sock_alloc_send_skb(sk, LL_RESERVED_SPACE(dev) + full_len, 1, &err); if (skb == NULL) { - IP6_INC_STATS(IPSTATS_MIB_OUTDISCARDS); + rcu_read_lock(); + IP6_INC_STATS(__in6_dev_get(dev), + IPSTATS_MIB_OUTDISCARDS); + rcu_read_unlock(); return; } skb_reserve(skb, LL_RESERVED_SPACE(dev)); - if (dev->hard_header) { - unsigned char ha[MAX_ADDR_LEN]; - ndisc_mc_map(snd_addr, ha, dev, 1); - if (dev->hard_header(skb, dev, ETH_P_IPV6, ha, NULL, full_len) < 0) - goto out; - } if (ipv6_get_lladdr(dev, &addr_buf)) { /* : @@ -1665,24 +1817,20 @@ static void igmp6_send(struct in6_addr *addr, struct net_device *dev, int type) idev = in6_dev_get(skb->dev); err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, skb->dev, - dev_queue_xmit); + mld_dev_queue_xmit); if (!err) { if (type == ICMPV6_MGM_REDUCTION) ICMP6_INC_STATS(idev, ICMP6_MIB_OUTGROUPMEMBREDUCTIONS); else ICMP6_INC_STATS(idev, ICMP6_MIB_OUTGROUPMEMBRESPONSES); ICMP6_INC_STATS(idev, ICMP6_MIB_OUTMSGS); - IP6_INC_STATS(IPSTATS_MIB_OUTMCASTPKTS); + IP6_INC_STATS(idev, IPSTATS_MIB_OUTMCASTPKTS); } else - IP6_INC_STATS(IPSTATS_MIB_OUTDISCARDS); + IP6_INC_STATS(idev, IPSTATS_MIB_OUTDISCARDS); if (likely(idev != NULL)) in6_dev_put(idev); return; - -out: - IP6_INC_STATS(IPSTATS_MIB_OUTDISCARDS); - kfree_skb(skb); } static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode, @@ -1693,7 +1841,7 @@ static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode, psf_prev = NULL; for (psf=pmc->mca_sources; psf; psf=psf->sf_next) { - if (ipv6_addr_cmp(&psf->sf_addr, psfsrc) == 0) + if (ipv6_addr_equal(&psf->sf_addr, psfsrc)) break; psf_prev = psf; } @@ -1722,8 +1870,9 @@ static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode, return rv; } -int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode, - int sfcount, struct in6_addr *psfsrc, int delta) +static int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca, + int sfmode, int sfcount, struct in6_addr *psfsrc, + int delta) { struct ifmcaddr6 *pmc; int changerec = 0; @@ -1733,7 +1882,7 @@ int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode, return -ENODEV; read_lock_bh(&idev->lock); for (pmc=idev->mc_list; pmc; pmc=pmc->next) { - if (ipv6_addr_cmp(pmca, &pmc->mca_addr) == 0) + if (ipv6_addr_equal(pmca, &pmc->mca_addr)) break; } if (!pmc) { @@ -1788,15 +1937,15 @@ static int ip6_mc_add1_src(struct ifmcaddr6 *pmc, int sfmode, psf_prev = NULL; for (psf=pmc->mca_sources; psf; psf=psf->sf_next) { - if (ipv6_addr_cmp(&psf->sf_addr, psfsrc) == 0) + if (ipv6_addr_equal(&psf->sf_addr, psfsrc)) break; psf_prev = psf; } if (!psf) { - psf = (struct ip6_sf_list *)kmalloc(sizeof(*psf), GFP_ATOMIC); + psf = kzalloc(sizeof(*psf), GFP_ATOMIC); if (!psf) return -ENOBUFS; - memset(psf, 0, sizeof(*psf)); + psf->sf_addr = *psfsrc; if (psf_prev) { psf_prev->sf_next = psf; @@ -1823,7 +1972,7 @@ static void sf_markstate(struct ifmcaddr6 *pmc) static int sf_setstate(struct ifmcaddr6 *pmc) { - struct ip6_sf_list *psf; + struct ip6_sf_list *psf, *dpsf; int mca_xcount = pmc->mca_sfcount[MCAST_EXCLUDE]; int qrv = pmc->idev->mc_qrv; int new_in, rv; @@ -1835,8 +1984,48 @@ static int sf_setstate(struct ifmcaddr6 *pmc) !psf->sf_count[MCAST_INCLUDE]; } else new_in = psf->sf_count[MCAST_INCLUDE] != 0; - if (new_in != psf->sf_oldin) { - psf->sf_crcount = qrv; + if (new_in) { + if (!psf->sf_oldin) { + struct ip6_sf_list *prev = NULL; + + for (dpsf=pmc->mca_tomb; dpsf; + dpsf=dpsf->sf_next) { + if (ipv6_addr_equal(&dpsf->sf_addr, + &psf->sf_addr)) + break; + prev = dpsf; + } + if (dpsf) { + if (prev) + prev->sf_next = dpsf->sf_next; + else + pmc->mca_tomb = dpsf->sf_next; + kfree(dpsf); + } + psf->sf_crcount = qrv; + rv++; + } + } else if (psf->sf_oldin) { + psf->sf_crcount = 0; + /* + * add or update "delete" records if an active filter + * is now inactive + */ + for (dpsf=pmc->mca_tomb; dpsf; dpsf=dpsf->sf_next) + if (ipv6_addr_equal(&dpsf->sf_addr, + &psf->sf_addr)) + break; + if (!dpsf) { + dpsf = (struct ip6_sf_list *) + kmalloc(sizeof(*dpsf), GFP_ATOMIC); + if (!dpsf) + continue; + *dpsf = *psf; + /* pmc->mca_lock held by callers */ + dpsf->sf_next = pmc->mca_tomb; + pmc->mca_tomb = dpsf; + } + dpsf->sf_crcount = qrv; rv++; } } @@ -1846,8 +2035,9 @@ static int sf_setstate(struct ifmcaddr6 *pmc) /* * Add multicast source filter list to the interface list */ -int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode, - int sfcount, struct in6_addr *psfsrc, int delta) +static int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, + int sfmode, int sfcount, struct in6_addr *psfsrc, + int delta) { struct ifmcaddr6 *pmc; int isexclude; @@ -1857,7 +2047,7 @@ int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode, return -ENODEV; read_lock_bh(&idev->lock); for (pmc=idev->mc_list; pmc; pmc=pmc->next) { - if (ipv6_addr_cmp(pmca, &pmc->mca_addr) == 0) + if (ipv6_addr_equal(pmca, &pmc->mca_addr)) break; } if (!pmc) { @@ -1880,7 +2070,8 @@ int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode, if (err) { int j; - pmc->mca_sfcount[sfmode]--; + if (!delta) + pmc->mca_sfcount[sfmode]--; for (j=0; jmca_sfcount[MCAST_EXCLUDE] != 0)) { @@ -1921,7 +2112,7 @@ static void ip6_mc_clear_src(struct ifmcaddr6 *pmc) } pmc->mca_sources = NULL; pmc->mca_sfmode = MCAST_EXCLUDE; - pmc->mca_sfcount[MCAST_EXCLUDE] = 0; + pmc->mca_sfcount[MCAST_INCLUDE] = 0; pmc->mca_sfcount[MCAST_EXCLUDE] = 1; } @@ -1949,11 +2140,14 @@ static void igmp6_join_group(struct ifmcaddr6 *ma) spin_unlock_bh(&ma->mca_lock); } -int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, - struct inet6_dev *idev) +static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, + struct inet6_dev *idev) { int err; + /* callers have the socket lock and a write lock on ipv6_sk_mc_lock, + * so no other readers or writers of iml or its sflist + */ if (iml->sflist == 0) { /* any-source empty exclude case */ return ip6_mc_del_src(idev, &iml->addr, iml->sfmode, 0, NULL, 0); @@ -2066,10 +2260,8 @@ void ipv6_mc_up(struct inet6_dev *idev) void ipv6_mc_init_dev(struct inet6_dev *idev) { - struct in6_addr maddr; - write_lock_bh(&idev->lock); - idev->mc_lock = RW_LOCK_UNLOCKED; + rwlock_init(&idev->mc_lock); idev->mc_gq_running = 0; init_timer(&idev->mc_gq_timer); idev->mc_gq_timer.data = (unsigned long) idev; @@ -2083,10 +2275,6 @@ void ipv6_mc_init_dev(struct inet6_dev *idev) idev->mc_maxdelay = IGMP6_UNSOLICITED_IVAL; idev->mc_v1_seen = 0; write_unlock_bh(&idev->lock); - - /* Add all-nodes address. */ - ipv6_addr_all_nodes(&maddr); - ipv6_dev_mc_inc(idev->dev, &maddr); } /* @@ -2108,7 +2296,12 @@ void ipv6_mc_destroy_dev(struct inet6_dev *idev) * addrconf.c has NULL'd out dev->ip6_ptr so in6_dev_get() will * fail. */ - __ipv6_dev_mc_dec(idev->dev, idev, &maddr); + __ipv6_dev_mc_dec(idev, &maddr); + + if (idev->cnf.forwarding) { + ipv6_addr_all_routers(&maddr); + __ipv6_dev_mc_dec(idev, &maddr); + } write_lock_bh(&idev->lock); while ((i = idev->mc_list) != NULL) { @@ -2220,7 +2413,7 @@ static int igmp6_mc_seq_show(struct seq_file *seq, void *v) struct igmp6_mc_iter_state *state = igmp6_mc_seq_private(seq); seq_printf(seq, - "%-4d %-15s %04x%04x%04x%04x%04x%04x%04x%04x %5d %08X %ld\n", + "%-4d %-15s " NIP6_SEQFMT " %5d %08X %ld\n", state->dev->ifindex, state->dev->name, NIP6(im->mca_addr), im->mca_users, im->mca_flags, @@ -2240,7 +2433,7 @@ static int igmp6_mc_seq_open(struct inode *inode, struct file *file) { struct seq_file *seq; int rc = -ENOMEM; - struct igmp6_mc_iter_state *s = kmalloc(sizeof(*s), GFP_KERNEL); + struct igmp6_mc_iter_state *s = kzalloc(sizeof(*s), GFP_KERNEL); if (!s) goto out; @@ -2251,7 +2444,6 @@ static int igmp6_mc_seq_open(struct inode *inode, struct file *file) seq = file->private_data; seq->private = s; - memset(s, 0, sizeof(*s)); out: return rc; out_kfree: @@ -2394,10 +2586,7 @@ static int igmp6_mcf_seq_show(struct seq_file *seq, void *v) "Source Address", "INC", "EXC"); } else { seq_printf(seq, - "%3d %6.6s " - "%04x%04x%04x%04x%04x%04x%04x%04x " - "%04x%04x%04x%04x%04x%04x%04x%04x " - "%6lu %6lu\n", + "%3d %6.6s " NIP6_SEQFMT " " NIP6_SEQFMT " %6lu %6lu\n", state->dev->ifindex, state->dev->name, NIP6(state->im->mca_addr), NIP6(psf->sf_addr), @@ -2418,7 +2607,7 @@ static int igmp6_mcf_seq_open(struct inode *inode, struct file *file) { struct seq_file *seq; int rc = -ENOMEM; - struct igmp6_mcf_iter_state *s = kmalloc(sizeof(*s), GFP_KERNEL); + struct igmp6_mcf_iter_state *s = kzalloc(sizeof(*s), GFP_KERNEL); if (!s) goto out; @@ -2429,7 +2618,6 @@ static int igmp6_mcf_seq_open(struct inode *inode, struct file *file) seq = file->private_data; seq->private = s; - memset(s, 0, sizeof(*s)); out: return rc; out_kfree: