Skip to content

Commit 6e2059b

Browse files
liuhangbindavem330
authored andcommitted
ipv4/igmp: init group mode as INCLUDE when join source group
Based on RFC3376 5.1 If no 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 a filter mode of INCLUDE and an empty source list. Which means a new multicast group should start with state IN(). Function ip_mc_join_group() works correctly for IGMP ASM(Any-Source Multicast) mode. It adds a group with state EX() and inits crcount to mc_qrv, so the kernel will send a TO_EX() report message after adding group. But for IGMPv3 SSM(Source-specific multicast) JOIN_SOURCE_GROUP mode, we split the group joining into two steps. First we join the group like ASM, i.e. via ip_mc_join_group(). So the state changes from IN() to EX(). Then we add the source-specific address with INCLUDE mode. So the state changes from EX() to IN(A). Before the first step sends a group change record, we finished the second step. So we will only send the second change record. i.e. TO_IN(A). Regarding the RFC stands, we should actually send an ALLOW(A) message for SSM JOIN_SOURCE_GROUP as the state should mimic the 'IN() to IN(A)' transition. The issue was exposed by commit a052517 ("net/multicast: should not send source list records when have filter mode change"). Before this change, we used to send both ALLOW(A) and TO_IN(A). After this change we only send TO_IN(A). Fix it by adding a new parameter to init group mode. Also add new wrapper functions so we don't need to change too much code. v1 -> v2: In my 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 an 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. Fixes: a052517 ("net/multicast: should not send source list records when have filter mode change") Reviewed-by: Stefano Brivio <[email protected]> Signed-off-by: Hangbin Liu <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 6bed5e2 commit 6e2059b

File tree

3 files changed

+46
-18
lines changed

3 files changed

+46
-18
lines changed

include/linux/igmp.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ struct ip_mc_list {
109109
extern int ip_check_mc_rcu(struct in_device *dev, __be32 mc_addr, __be32 src_addr, u8 proto);
110110
extern int igmp_rcv(struct sk_buff *);
111111
extern int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr);
112+
extern int ip_mc_join_group_ssm(struct sock *sk, struct ip_mreqn *imr,
113+
unsigned int mode);
112114
extern int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr);
113115
extern void ip_mc_drop_socket(struct sock *sk);
114116
extern int ip_mc_source(int add, int omode, struct sock *sk,

net/ipv4/igmp.c

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,13 +1200,14 @@ static void igmpv3_del_delrec(struct in_device *in_dev, struct ip_mc_list *im)
12001200
spin_lock_bh(&im->lock);
12011201
if (pmc) {
12021202
im->interface = pmc->interface;
1203-
im->crcount = in_dev->mr_qrv ?: net->ipv4.sysctl_igmp_qrv;
12041203
im->sfmode = pmc->sfmode;
12051204
if (pmc->sfmode == MCAST_INCLUDE) {
12061205
im->tomb = pmc->tomb;
12071206
im->sources = pmc->sources;
12081207
for (psf = im->sources; psf; psf = psf->sf_next)
1209-
psf->sf_crcount = im->crcount;
1208+
psf->sf_crcount = in_dev->mr_qrv ?: net->ipv4.sysctl_igmp_qrv;
1209+
} else {
1210+
im->crcount = in_dev->mr_qrv ?: net->ipv4.sysctl_igmp_qrv;
12101211
}
12111212
in_dev_put(pmc->interface);
12121213
kfree(pmc);
@@ -1288,7 +1289,7 @@ static void igmp_group_dropped(struct ip_mc_list *im)
12881289
#endif
12891290
}
12901291

1291-
static void igmp_group_added(struct ip_mc_list *im)
1292+
static void igmp_group_added(struct ip_mc_list *im, unsigned int mode)
12921293
{
12931294
struct in_device *in_dev = im->interface;
12941295
#ifdef CONFIG_IP_MULTICAST
@@ -1316,7 +1317,13 @@ static void igmp_group_added(struct ip_mc_list *im)
13161317
}
13171318
/* else, v3 */
13181319

1319-
im->crcount = in_dev->mr_qrv ?: net->ipv4.sysctl_igmp_qrv;
1320+
/* Based on RFC3376 5.1, for newly added INCLUDE SSM, we should
1321+
* not send filter-mode change record as the mode should be from
1322+
* IN() to IN(A).
1323+
*/
1324+
if (mode == MCAST_EXCLUDE)
1325+
im->crcount = in_dev->mr_qrv ?: net->ipv4.sysctl_igmp_qrv;
1326+
13201327
igmp_ifc_event(in_dev);
13211328
#endif
13221329
}
@@ -1381,8 +1388,7 @@ static void ip_mc_hash_remove(struct in_device *in_dev,
13811388
/*
13821389
* A socket has joined a multicast group on device dev.
13831390
*/
1384-
1385-
void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
1391+
void __ip_mc_inc_group(struct in_device *in_dev, __be32 addr, unsigned int mode)
13861392
{
13871393
struct ip_mc_list *im;
13881394
#ifdef CONFIG_IP_MULTICAST
@@ -1394,7 +1400,7 @@ void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
13941400
for_each_pmc_rtnl(in_dev, im) {
13951401
if (im->multiaddr == addr) {
13961402
im->users++;
1397-
ip_mc_add_src(in_dev, &addr, MCAST_EXCLUDE, 0, NULL, 0);
1403+
ip_mc_add_src(in_dev, &addr, mode, 0, NULL, 0);
13981404
goto out;
13991405
}
14001406
}
@@ -1408,8 +1414,8 @@ void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
14081414
in_dev_hold(in_dev);
14091415
im->multiaddr = addr;
14101416
/* initial mode is (EX, empty) */
1411-
im->sfmode = MCAST_EXCLUDE;
1412-
im->sfcount[MCAST_EXCLUDE] = 1;
1417+
im->sfmode = mode;
1418+
im->sfcount[mode] = 1;
14131419
refcount_set(&im->refcnt, 1);
14141420
spin_lock_init(&im->lock);
14151421
#ifdef CONFIG_IP_MULTICAST
@@ -1426,12 +1432,17 @@ void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
14261432
#ifdef CONFIG_IP_MULTICAST
14271433
igmpv3_del_delrec(in_dev, im);
14281434
#endif
1429-
igmp_group_added(im);
1435+
igmp_group_added(im, mode);
14301436
if (!in_dev->dead)
14311437
ip_rt_multicast_event(in_dev);
14321438
out:
14331439
return;
14341440
}
1441+
1442+
void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
1443+
{
1444+
__ip_mc_inc_group(in_dev, addr, MCAST_EXCLUDE);
1445+
}
14351446
EXPORT_SYMBOL(ip_mc_inc_group);
14361447

14371448
static int ip_mc_check_iphdr(struct sk_buff *skb)
@@ -1688,7 +1699,7 @@ void ip_mc_remap(struct in_device *in_dev)
16881699
#ifdef CONFIG_IP_MULTICAST
16891700
igmpv3_del_delrec(in_dev, pmc);
16901701
#endif
1691-
igmp_group_added(pmc);
1702+
igmp_group_added(pmc, pmc->sfmode);
16921703
}
16931704
}
16941705

@@ -1751,7 +1762,7 @@ void ip_mc_up(struct in_device *in_dev)
17511762
#ifdef CONFIG_IP_MULTICAST
17521763
igmpv3_del_delrec(in_dev, pmc);
17531764
#endif
1754-
igmp_group_added(pmc);
1765+
igmp_group_added(pmc, pmc->sfmode);
17551766
}
17561767
}
17571768

@@ -2130,8 +2141,8 @@ static void ip_mc_clear_src(struct ip_mc_list *pmc)
21302141

21312142
/* Join a multicast group
21322143
*/
2133-
2134-
int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr)
2144+
static int __ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr,
2145+
unsigned int mode)
21352146
{
21362147
__be32 addr = imr->imr_multiaddr.s_addr;
21372148
struct ip_mc_socklist *iml, *i;
@@ -2172,15 +2183,30 @@ int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr)
21722183
memcpy(&iml->multi, imr, sizeof(*imr));
21732184
iml->next_rcu = inet->mc_list;
21742185
iml->sflist = NULL;
2175-
iml->sfmode = MCAST_EXCLUDE;
2186+
iml->sfmode = mode;
21762187
rcu_assign_pointer(inet->mc_list, iml);
2177-
ip_mc_inc_group(in_dev, addr);
2188+
__ip_mc_inc_group(in_dev, addr, mode);
21782189
err = 0;
21792190
done:
21802191
return err;
21812192
}
2193+
2194+
/* Join ASM (Any-Source Multicast) group
2195+
*/
2196+
int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr)
2197+
{
2198+
return __ip_mc_join_group(sk, imr, MCAST_EXCLUDE);
2199+
}
21822200
EXPORT_SYMBOL(ip_mc_join_group);
21832201

2202+
/* Join SSM (Source-Specific Multicast) group
2203+
*/
2204+
int ip_mc_join_group_ssm(struct sock *sk, struct ip_mreqn *imr,
2205+
unsigned int mode)
2206+
{
2207+
return __ip_mc_join_group(sk, imr, mode);
2208+
}
2209+
21842210
static int ip_mc_leave_src(struct sock *sk, struct ip_mc_socklist *iml,
21852211
struct in_device *in_dev)
21862212
{

net/ipv4/ip_sockglue.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ static int do_ip_setsockopt(struct sock *sk, int level,
984984
mreq.imr_multiaddr.s_addr = mreqs.imr_multiaddr;
985985
mreq.imr_address.s_addr = mreqs.imr_interface;
986986
mreq.imr_ifindex = 0;
987-
err = ip_mc_join_group(sk, &mreq);
987+
err = ip_mc_join_group_ssm(sk, &mreq, MCAST_INCLUDE);
988988
if (err && err != -EADDRINUSE)
989989
break;
990990
omode = MCAST_INCLUDE;
@@ -1061,7 +1061,7 @@ static int do_ip_setsockopt(struct sock *sk, int level,
10611061
mreq.imr_multiaddr = psin->sin_addr;
10621062
mreq.imr_address.s_addr = 0;
10631063
mreq.imr_ifindex = greqs.gsr_interface;
1064-
err = ip_mc_join_group(sk, &mreq);
1064+
err = ip_mc_join_group_ssm(sk, &mreq, MCAST_INCLUDE);
10651065
if (err && err != -EADDRINUSE)
10661066
break;
10671067
greqs.gsr_interface = mreq.imr_ifindex;

0 commit comments

Comments
 (0)