diff options
author | David L Stevens <dlstevens@us.ibm.com> | 2005-06-21 16:58:25 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2005-06-21 16:58:25 -0400 |
commit | c9e3e8b6958e02230079e6817862ea2968509866 (patch) | |
tree | 296b30b9e208d37b1cdfa6d40ad785abb1487bd9 /net/ipv6 | |
parent | 0d51aa80a9b1db43920c0770c3bb842dd823c005 (diff) |
[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>
Diffstat (limited to 'net/ipv6')
-rw-r--r-- | net/ipv6/ipv6_sockglue.c | 5 | ||||
-rw-r--r-- | net/ipv6/mcast.c | 21 |
2 files changed, 24 insertions, 2 deletions
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c index 279ab86be66..f3ef4c38d31 100644 --- a/net/ipv6/ipv6_sockglue.c +++ b/net/ipv6/ipv6_sockglue.c | |||
@@ -423,11 +423,12 @@ done: | |||
423 | psin6 = (struct sockaddr_in6 *)&greqs.gsr_group; | 423 | psin6 = (struct sockaddr_in6 *)&greqs.gsr_group; |
424 | retv = ipv6_sock_mc_join(sk, greqs.gsr_interface, | 424 | retv = ipv6_sock_mc_join(sk, greqs.gsr_interface, |
425 | &psin6->sin6_addr); | 425 | &psin6->sin6_addr); |
426 | if (retv) | 426 | /* prior join w/ different source is ok */ |
427 | if (retv && retv != -EADDRINUSE) | ||
427 | break; | 428 | break; |
428 | omode = MCAST_INCLUDE; | 429 | omode = MCAST_INCLUDE; |
429 | add = 1; | 430 | add = 1; |
430 | } else /*IP_DROP_SOURCE_MEMBERSHIP */ { | 431 | } else /* MCAST_LEAVE_SOURCE_GROUP */ { |
431 | omode = MCAST_INCLUDE; | 432 | omode = MCAST_INCLUDE; |
432 | add = 0; | 433 | add = 0; |
433 | } | 434 | } |
diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 393b6e6f50a..c0ca92e8230 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c | |||
@@ -188,6 +188,16 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex, struct in6_addr *addr) | |||
188 | if (!ipv6_addr_is_multicast(addr)) | 188 | if (!ipv6_addr_is_multicast(addr)) |
189 | return -EINVAL; | 189 | return -EINVAL; |
190 | 190 | ||
191 | read_lock_bh(&ipv6_sk_mc_lock); | ||
192 | for (mc_lst=np->ipv6_mc_list; mc_lst; mc_lst=mc_lst->next) { | ||
193 | if ((ifindex == 0 || mc_lst->ifindex == ifindex) && | ||
194 | ipv6_addr_equal(&mc_lst->addr, addr)) { | ||
195 | read_unlock_bh(&ipv6_sk_mc_lock); | ||
196 | return -EADDRINUSE; | ||
197 | } | ||
198 | } | ||
199 | read_unlock_bh(&ipv6_sk_mc_lock); | ||
200 | |||
191 | mc_lst = sock_kmalloc(sk, sizeof(struct ipv6_mc_socklist), GFP_KERNEL); | 201 | mc_lst = sock_kmalloc(sk, sizeof(struct ipv6_mc_socklist), GFP_KERNEL); |
192 | 202 | ||
193 | if (mc_lst == NULL) | 203 | if (mc_lst == NULL) |
@@ -349,6 +359,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk, | |||
349 | struct ipv6_pinfo *inet6 = inet6_sk(sk); | 359 | struct ipv6_pinfo *inet6 = inet6_sk(sk); |
350 | struct ip6_sf_socklist *psl; | 360 | struct ip6_sf_socklist *psl; |
351 | int i, j, rv; | 361 | int i, j, rv; |
362 | int leavegroup = 0; | ||
352 | int err; | 363 | int err; |
353 | 364 | ||
354 | if (pgsr->gsr_group.ss_family != AF_INET6 || | 365 | if (pgsr->gsr_group.ss_family != AF_INET6 || |
@@ -368,6 +379,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk, | |||
368 | 379 | ||
369 | err = -EADDRNOTAVAIL; | 380 | err = -EADDRNOTAVAIL; |
370 | 381 | ||
382 | read_lock_bh(&ipv6_sk_mc_lock); | ||
371 | for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) { | 383 | for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) { |
372 | if (pgsr->gsr_interface && pmc->ifindex != pgsr->gsr_interface) | 384 | if (pgsr->gsr_interface && pmc->ifindex != pgsr->gsr_interface) |
373 | continue; | 385 | continue; |
@@ -401,6 +413,12 @@ int ip6_mc_source(int add, int omode, struct sock *sk, | |||
401 | if (rv) /* source not found */ | 413 | if (rv) /* source not found */ |
402 | goto done; | 414 | goto done; |
403 | 415 | ||
416 | /* special case - (INCLUDE, empty) == LEAVE_GROUP */ | ||
417 | if (psl->sl_count == 1 && omode == MCAST_INCLUDE) { | ||
418 | leavegroup = 1; | ||
419 | goto done; | ||
420 | } | ||
421 | |||
404 | /* update the interface filter */ | 422 | /* update the interface filter */ |
405 | ip6_mc_del_src(idev, group, omode, 1, source, 1); | 423 | ip6_mc_del_src(idev, group, omode, 1, source, 1); |
406 | 424 | ||
@@ -453,9 +471,12 @@ int ip6_mc_source(int add, int omode, struct sock *sk, | |||
453 | /* update the interface list */ | 471 | /* update the interface list */ |
454 | ip6_mc_add_src(idev, group, omode, 1, source, 1); | 472 | ip6_mc_add_src(idev, group, omode, 1, source, 1); |
455 | done: | 473 | done: |
474 | read_unlock_bh(&ipv6_sk_mc_lock); | ||
456 | read_unlock_bh(&idev->lock); | 475 | read_unlock_bh(&idev->lock); |
457 | in6_dev_put(idev); | 476 | in6_dev_put(idev); |
458 | dev_put(dev); | 477 | dev_put(dev); |
478 | if (leavegroup) | ||
479 | return ipv6_sock_mc_drop(sk, pgsr->gsr_interface, group); | ||
459 | return err; | 480 | return err; |
460 | } | 481 | } |
461 | 482 | ||