[PATCH net-next v2] ipv4/ipv6: preserve multicast filters after a failed source leave
From: Xia Yan
Date: Wed Nov 04 2026 - 01:15:06 EST
After joining a group with IP_ADD_MEMBERSHIP or IPV6_JOIN_GROUP,
MCAST_LEAVE_SOURCE_GROUP for an unjoined source returns EADDRNOTAVAIL
but switches the socket's filter mode, so the existing membership stops
receiving. Leaving and rejoining the group restores reception.
IP_DROP_SOURCE_MEMBERSHIP has the same problem.
ip_mc_source() and ip6_mc_source() allow an empty-filter mode switch before
checking whether a source list exists for deletion. The failed request
changes EXCLUDE {} to INCLUDE {}, so the socket no longer accepts packets
from any source.
Check for a missing source list before changing the filter mode. Preserve
EADDRNOTAVAIL for the rejected deletion and keep mode switches for valid
source additions.
Found while reviewing the error paths of ip_mc_source() and
ip6_mc_source().
The reproducer uses socket calls without kernel changes or fault injection.
Tested with a UDP socket reproducer under UML using the IPv4 generic and
legacy APIs and the IPv6 generic API. All three paths lose reception
before this change and keep receiving with the fix. Source joins and
leaves, block/unblock, last-source leave, and ordinary leave/rejoin also
pass.
W=1 x86_64 builds with derived allmodconfig and allyesconfig configurations
also pass, with no changes to the warning sets.
Signed-off-by: Xia Yan <yanxia@xxxxxxxxxxxxxxxxxxxx>
---
Changes in v2:
- Target net-next and drop the Fixes and stable tags, as requested by
Ido Schimmel.
- No code changes.
v1: https://lore.kernel.org/netdev/20261030063603.697510-1-yanxia@xxxxxxxxxxxxxxxxxxxx/
Validation was performed for v1 on net fcc00b15cb21a6de76826e5012b2c758505f5e65.
The two modified files are identical at the net-next base commit.
For the x86_64 builds, the derived configurations disable WERROR and
KASAN; the derived allyesconfig also disables KUNIT_ALL_TESTS. The complete
before/after warning sets are identical for each configuration.
net/ipv4/igmp.c | 7 ++++---
net/ipv6/mcast.c | 7 ++++---
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index 144fca1..9c38045 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -2520,6 +2520,10 @@ int ip_mc_source(int add, int omode, struct sock *sk, struct
err = -EINVAL;
goto done;
}
+ psl = rtnl_dereference(pmc->sflist);
+ if (!add && !psl)
+ goto done; /* err = -EADDRNOTAVAIL */
+
/* if a source filter was set, must be the same mode as before */
if (pmc->sflist) {
if (pmc->sfmode != omode) {
@@ -2534,10 +2538,7 @@ int ip_mc_source(int add, int omode, struct sock *sk, struct
pmc->sfmode = omode;
}
- psl = rtnl_dereference(pmc->sflist);
if (!add) {
- if (!psl)
- goto done; /* err = -EADDRNOTAVAIL */
rv = !0;
for (i = 0; i < psl->sl_count; i++) {
rv = memcmp(&psl->sl_addr[i], &mreqs->imr_sourceaddr,
diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index ecef55f..2ade335 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -395,6 +395,10 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
err = -EINVAL;
goto done;
}
+ psl = sock_dereference(pmc->sflist, sk);
+ if (!add && !psl)
+ goto done; /* err = -EADDRNOTAVAIL */
+
/* if a source filter was set, must be the same mode as before */
if (rcu_access_pointer(pmc->sflist)) {
if (pmc->sfmode != omode) {
@@ -408,10 +412,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
pmc->sfmode = omode;
}
- psl = sock_dereference(pmc->sflist, sk);
if (!add) {
- if (!psl)
- goto done; /* err = -EADDRNOTAVAIL */
for (i = 0; i < psl->sl_count; i++) {
if (ipv6_addr_equal(&psl->sl_addr[i], source))
break;
base-commit: 7b6ab4dbc156e2f96419715130b3d5738a306c93
--
2.39.0.windows.2