Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ipv6/mcast: init as INCLUDE when join SSM INCLUDE group

This an IPv6 version patch of "ipv4/igmp: init group mode as INCLUDE when
join source group". From RFC3810, part 6.1:

If no per-interface state existed for that
multicast address before the change (i.e., the change consisted of
creating a new per-interface record), or if no state exists after the
change (i.e., the change consisted of deleting a per-interface
record), then the "non-existent" state is considered to have an
INCLUDE filter mode and an empty source list.

Which means a new multicast group should start with state IN(). Currently,
for MLDv2 SSM JOIN_SOURCE_GROUP mode, we first call ipv6_sock_mc_join(),
then ip6_mc_source(), which will trigger a TO_IN() message instead of
ALLOW().

The issue was exposed by commit a052517a8ff65 ("net/multicast: should not
send source list records when have filter mode change"). Before this change,
we sent both ALLOW(A) and TO_IN(A). Now, we only send TO_IN(A).

Fix it by adding a new parameter to init group mode. Also add some wrapper
functions to avoid changing too much code.

v1 -> v2:
In the first version I only cleared the group change record. But this is not
enough. Because when a new group join, it will init as EXCLUDE and trigger
a filter mode change in ip/ip6_mc_add_src(), which will clear all source
addresses sf_crcount. This will prevent early joined address sending state
change records if multi source addressed joined at the same time.

In v2 patch, I fixed it by directly initializing the mode to INCLUDE for SSM
JOIN_SOURCE_GROUP. I also split the original patch into two separated patches
for IPv4 and IPv6.

There is also a difference between v4 and v6 version. For IPv6, when the
interface goes down and up, we will send correct state change record with
unspecified IPv6 address (::) with function ipv6_mc_up(). But after DAD is
completed, we resend the change record TO_IN() in mld_send_initial_cr().
Fix it by sending ALLOW() for INCLUDE mode in mld_send_initial_cr().

Fixes: a052517a8ff65 ("net/multicast: should not send source list records when have filter mode change")
Reviewed-by: Stefano Brivio <sbrivio@redhat.com>
Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Hangbin Liu and committed by
David S. Miller
c7ea20c9 6e2059b5

+50 -21
+2
include/net/ipv6.h
··· 1100 1100 1101 1101 int ipv6_sock_mc_join(struct sock *sk, int ifindex, 1102 1102 const struct in6_addr *addr); 1103 + int ipv6_sock_mc_join_ssm(struct sock *sk, int ifindex, 1104 + const struct in6_addr *addr, unsigned int mode); 1103 1105 int ipv6_sock_mc_drop(struct sock *sk, int ifindex, 1104 1106 const struct in6_addr *addr); 1105 1107 #endif /* _NET_IPV6_H */
+3 -2
net/ipv6/ipv6_sockglue.c
··· 729 729 struct sockaddr_in6 *psin6; 730 730 731 731 psin6 = (struct sockaddr_in6 *)&greqs.gsr_group; 732 - retv = ipv6_sock_mc_join(sk, greqs.gsr_interface, 733 - &psin6->sin6_addr); 732 + retv = ipv6_sock_mc_join_ssm(sk, greqs.gsr_interface, 733 + &psin6->sin6_addr, 734 + MCAST_INCLUDE); 734 735 /* prior join w/ different source is ok */ 735 736 if (retv && retv != -EADDRINUSE) 736 737 break;
+45 -19
net/ipv6/mcast.c
··· 95 95 int delta); 96 96 static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, 97 97 struct inet6_dev *idev); 98 + static int __ipv6_dev_mc_inc(struct net_device *dev, 99 + const struct in6_addr *addr, unsigned int mode); 98 100 99 101 #define MLD_QRV_DEFAULT 2 100 102 /* RFC3810, 9.2. Query Interval */ ··· 134 132 return iv > 0 ? iv : 1; 135 133 } 136 134 137 - int ipv6_sock_mc_join(struct sock *sk, int ifindex, const struct in6_addr *addr) 135 + static int __ipv6_sock_mc_join(struct sock *sk, int ifindex, 136 + const struct in6_addr *addr, unsigned int mode) 138 137 { 139 138 struct net_device *dev = NULL; 140 139 struct ipv6_mc_socklist *mc_lst; ··· 182 179 } 183 180 184 181 mc_lst->ifindex = dev->ifindex; 185 - mc_lst->sfmode = MCAST_EXCLUDE; 182 + mc_lst->sfmode = mode; 186 183 rwlock_init(&mc_lst->sflock); 187 184 mc_lst->sflist = NULL; 188 185 ··· 190 187 * now add/increase the group membership on the device 191 188 */ 192 189 193 - err = ipv6_dev_mc_inc(dev, addr); 190 + err = __ipv6_dev_mc_inc(dev, addr, mode); 194 191 195 192 if (err) { 196 193 sock_kfree_s(sk, mc_lst, sizeof(*mc_lst)); ··· 202 199 203 200 return 0; 204 201 } 202 + 203 + int ipv6_sock_mc_join(struct sock *sk, int ifindex, const struct in6_addr *addr) 204 + { 205 + return __ipv6_sock_mc_join(sk, ifindex, addr, MCAST_EXCLUDE); 206 + } 205 207 EXPORT_SYMBOL(ipv6_sock_mc_join); 208 + 209 + int ipv6_sock_mc_join_ssm(struct sock *sk, int ifindex, 210 + const struct in6_addr *addr, unsigned int mode) 211 + { 212 + return __ipv6_sock_mc_join(sk, ifindex, addr, mode); 213 + } 206 214 207 215 /* 208 216 * socket leave on multicast group ··· 660 646 return rv; 661 647 } 662 648 663 - static void igmp6_group_added(struct ifmcaddr6 *mc) 649 + static void igmp6_group_added(struct ifmcaddr6 *mc, unsigned int mode) 664 650 { 665 651 struct net_device *dev = mc->idev->dev; 666 652 char buf[MAX_ADDR_LEN]; ··· 686 672 } 687 673 /* else v2 */ 688 674 689 - mc->mca_crcount = mc->idev->mc_qrv; 675 + /* Based on RFC3810 6.1, for newly added INCLUDE SSM, we 676 + * should not send filter-mode change record as the mode 677 + * should be from IN() to IN(A). 678 + */ 679 + if (mode == MCAST_EXCLUDE) 680 + mc->mca_crcount = mc->idev->mc_qrv; 681 + 690 682 mld_ifc_event(mc->idev); 691 683 } 692 684 ··· 790 770 spin_lock_bh(&im->mca_lock); 791 771 if (pmc) { 792 772 im->idev = pmc->idev; 793 - im->mca_crcount = idev->mc_qrv; 794 773 im->mca_sfmode = pmc->mca_sfmode; 795 774 if (pmc->mca_sfmode == MCAST_INCLUDE) { 796 775 im->mca_tomb = pmc->mca_tomb; 797 776 im->mca_sources = pmc->mca_sources; 798 777 for (psf = im->mca_sources; psf; psf = psf->sf_next) 799 - psf->sf_crcount = im->mca_crcount; 778 + psf->sf_crcount = idev->mc_qrv; 779 + } else { 780 + im->mca_crcount = idev->mc_qrv; 800 781 } 801 782 in6_dev_put(pmc->idev); 802 783 kfree(pmc); ··· 852 831 } 853 832 854 833 static struct ifmcaddr6 *mca_alloc(struct inet6_dev *idev, 855 - const struct in6_addr *addr) 834 + const struct in6_addr *addr, 835 + unsigned int mode) 856 836 { 857 837 struct ifmcaddr6 *mc; 858 838 ··· 871 849 refcount_set(&mc->mca_refcnt, 1); 872 850 spin_lock_init(&mc->mca_lock); 873 851 874 - /* initial mode is (EX, empty) */ 875 - mc->mca_sfmode = MCAST_EXCLUDE; 876 - mc->mca_sfcount[MCAST_EXCLUDE] = 1; 852 + mc->mca_sfmode = mode; 853 + mc->mca_sfcount[mode] = 1; 877 854 878 855 if (ipv6_addr_is_ll_all_nodes(&mc->mca_addr) || 879 856 IPV6_ADDR_MC_SCOPE(&mc->mca_addr) < IPV6_ADDR_SCOPE_LINKLOCAL) ··· 884 863 /* 885 864 * device multicast group inc (add if not found) 886 865 */ 887 - int ipv6_dev_mc_inc(struct net_device *dev, const struct in6_addr *addr) 866 + static int __ipv6_dev_mc_inc(struct net_device *dev, 867 + const struct in6_addr *addr, unsigned int mode) 888 868 { 889 869 struct ifmcaddr6 *mc; 890 870 struct inet6_dev *idev; ··· 909 887 if (ipv6_addr_equal(&mc->mca_addr, addr)) { 910 888 mc->mca_users++; 911 889 write_unlock_bh(&idev->lock); 912 - ip6_mc_add_src(idev, &mc->mca_addr, MCAST_EXCLUDE, 0, 913 - NULL, 0); 890 + ip6_mc_add_src(idev, &mc->mca_addr, mode, 0, NULL, 0); 914 891 in6_dev_put(idev); 915 892 return 0; 916 893 } 917 894 } 918 895 919 - mc = mca_alloc(idev, addr); 896 + mc = mca_alloc(idev, addr, mode); 920 897 if (!mc) { 921 898 write_unlock_bh(&idev->lock); 922 899 in6_dev_put(idev); ··· 932 911 write_unlock_bh(&idev->lock); 933 912 934 913 mld_del_delrec(idev, mc); 935 - igmp6_group_added(mc); 914 + igmp6_group_added(mc, mode); 936 915 ma_put(mc); 937 916 return 0; 917 + } 918 + 919 + int ipv6_dev_mc_inc(struct net_device *dev, const struct in6_addr *addr) 920 + { 921 + return __ipv6_dev_mc_inc(dev, addr, MCAST_EXCLUDE); 938 922 } 939 923 940 924 /* ··· 1777 1751 1778 1752 psf_next = psf->sf_next; 1779 1753 1780 - if (!is_in(pmc, psf, type, gdeleted, sdeleted)) { 1754 + if (!is_in(pmc, psf, type, gdeleted, sdeleted) && !crsend) { 1781 1755 psf_prev = psf; 1782 1756 continue; 1783 1757 } ··· 2092 2066 if (pmc->mca_sfcount[MCAST_EXCLUDE]) 2093 2067 type = MLD2_CHANGE_TO_EXCLUDE; 2094 2068 else 2095 - type = MLD2_CHANGE_TO_INCLUDE; 2069 + type = MLD2_ALLOW_NEW_SOURCES; 2096 2070 skb = add_grec(skb, pmc, type, 0, 0, 1); 2097 2071 spin_unlock_bh(&pmc->mca_lock); 2098 2072 } ··· 2572 2546 ipv6_mc_reset(idev); 2573 2547 for (i = idev->mc_list; i; i = i->next) { 2574 2548 mld_del_delrec(idev, i); 2575 - igmp6_group_added(i); 2549 + igmp6_group_added(i, i->mca_sfmode); 2576 2550 } 2577 2551 read_unlock_bh(&idev->lock); 2578 2552 }