Commit c9e3e8b6 authored by David L Stevens's avatar David L Stevens Committed by David S. Miller

[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: default avatarDavid L Stevens <dlstevens@us.ibm.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 0d51aa80
...@@ -423,11 +423,12 @@ done: ...@@ -423,11 +423,12 @@ done:
psin6 = (struct sockaddr_in6 *)&greqs.gsr_group; psin6 = (struct sockaddr_in6 *)&greqs.gsr_group;
retv = ipv6_sock_mc_join(sk, greqs.gsr_interface, retv = ipv6_sock_mc_join(sk, greqs.gsr_interface,
&psin6->sin6_addr); &psin6->sin6_addr);
if (retv) /* prior join w/ different source is ok */
if (retv && retv != -EADDRINUSE)
break; break;
omode = MCAST_INCLUDE; omode = MCAST_INCLUDE;
add = 1; add = 1;
} else /*IP_DROP_SOURCE_MEMBERSHIP */ { } else /* MCAST_LEAVE_SOURCE_GROUP */ {
omode = MCAST_INCLUDE; omode = MCAST_INCLUDE;
add = 0; add = 0;
} }
......
...@@ -188,6 +188,16 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex, struct in6_addr *addr) ...@@ -188,6 +188,16 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex, struct in6_addr *addr)
if (!ipv6_addr_is_multicast(addr)) if (!ipv6_addr_is_multicast(addr))
return -EINVAL; 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); mc_lst = sock_kmalloc(sk, sizeof(struct ipv6_mc_socklist), GFP_KERNEL);
if (mc_lst == NULL) if (mc_lst == NULL)
...@@ -349,6 +359,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk, ...@@ -349,6 +359,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
struct ipv6_pinfo *inet6 = inet6_sk(sk); struct ipv6_pinfo *inet6 = inet6_sk(sk);
struct ip6_sf_socklist *psl; struct ip6_sf_socklist *psl;
int i, j, rv; int i, j, rv;
int leavegroup = 0;
int err; int err;
if (pgsr->gsr_group.ss_family != AF_INET6 || if (pgsr->gsr_group.ss_family != AF_INET6 ||
...@@ -368,6 +379,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk, ...@@ -368,6 +379,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
err = -EADDRNOTAVAIL; err = -EADDRNOTAVAIL;
read_lock_bh(&ipv6_sk_mc_lock);
for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) { for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) {
if (pgsr->gsr_interface && pmc->ifindex != pgsr->gsr_interface) if (pgsr->gsr_interface && pmc->ifindex != pgsr->gsr_interface)
continue; continue;
...@@ -401,6 +413,12 @@ int ip6_mc_source(int add, int omode, struct sock *sk, ...@@ -401,6 +413,12 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
if (rv) /* source not found */ if (rv) /* source not found */
goto done; goto done;
/* special case - (INCLUDE, empty) == LEAVE_GROUP */
if (psl->sl_count == 1 && omode == MCAST_INCLUDE) {
leavegroup = 1;
goto done;
}
/* update the interface filter */ /* update the interface filter */
ip6_mc_del_src(idev, group, omode, 1, source, 1); 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, ...@@ -453,9 +471,12 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
/* update the interface list */ /* update the interface list */
ip6_mc_add_src(idev, group, omode, 1, source, 1); ip6_mc_add_src(idev, group, omode, 1, source, 1);
done: done:
read_unlock_bh(&ipv6_sk_mc_lock);
read_unlock_bh(&idev->lock); read_unlock_bh(&idev->lock);
in6_dev_put(idev); in6_dev_put(idev);
dev_put(dev); dev_put(dev);
if (leavegroup)
return ipv6_sock_mc_drop(sk, pgsr->gsr_interface, group);
return err; return err;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment