mirror of
https://github.com/torvalds/linux.git
synced 2024-12-25 12:21:37 +00:00
[IPV6]: multicast join and misc
Here is a simplified version of the patch to fix a bug in IPv6 multicasting. It: 1) adds existence check & EADDRINUSE error for regular joins 2) adds an exception for EADDRINUSE in the source-specific multicast join (where a prior join is ok) 3) adds a missing/needed read_lock on sock_mc_list; would've raced with destroying the socket on interface down without 4) adds a "leave group" in the (INCLUDE, empty) source filter case. This frees unneeded socket buffer memory, but also prevents an inappropriate interaction among the 8 socket options that mess with this. Some would fail as if in the group when you aren't really. Item #4 had a locking bug in the last version of this patch; rather than removing the idev->lock read lock only, I've simplified it to remove all lock state in the path and treat it as a direct "leave group" call for the (INCLUDE,empty) case it covers. Tested on an MP machine. :-) Much thanks to HoerdtMickael <hoerdt@clarinet.u-strasbg.fr> who reported the original bug. Signed-off-by: David L Stevens <dlstevens@us.ibm.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
0d51aa80a9
commit
c9e3e8b695
@ -423,11 +423,12 @@ done:
|
||||
psin6 = (struct sockaddr_in6 *)&greqs.gsr_group;
|
||||
retv = ipv6_sock_mc_join(sk, greqs.gsr_interface,
|
||||
&psin6->sin6_addr);
|
||||
if (retv)
|
||||
/* prior join w/ different source is ok */
|
||||
if (retv && retv != -EADDRINUSE)
|
||||
break;
|
||||
omode = MCAST_INCLUDE;
|
||||
add = 1;
|
||||
} else /*IP_DROP_SOURCE_MEMBERSHIP */ {
|
||||
} else /* MCAST_LEAVE_SOURCE_GROUP */ {
|
||||
omode = MCAST_INCLUDE;
|
||||
add = 0;
|
||||
}
|
||||
|
@ -188,6 +188,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)
|
||||
@ -349,6 +359,7 @@ 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 err;
|
||||
|
||||
if (pgsr->gsr_group.ss_family != AF_INET6 ||
|
||||
@ -368,6 +379,7 @@ 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;
|
||||
@ -401,6 +413,12 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
|
||||
if (rv) /* source not found */
|
||||
goto done;
|
||||
|
||||
/* 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);
|
||||
|
||||
@ -453,9 +471,12 @@ 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:
|
||||
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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user