2019-05-27 09:55:01 +03:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2005-04-17 02:20:36 +04:00
|
|
|
/*
|
|
|
|
* Linux NET3: Internet Group Management Protocol [IGMP]
|
|
|
|
*
|
|
|
|
* This code implements the IGMP protocol as defined in RFC1112. There has
|
|
|
|
* been a further revision of this protocol since which is now supported.
|
|
|
|
*
|
|
|
|
* If you have trouble with this module be careful what gcc you have used,
|
|
|
|
* the older version didn't come out right using gcc 2.5.8, the newer one
|
|
|
|
* seems to fall out with gcc 2.6.2.
|
|
|
|
*
|
|
|
|
* Authors:
|
2008-10-14 06:01:08 +04:00
|
|
|
* Alan Cox <alan@lxorguk.ukuu.org.uk>
|
2005-04-17 02:20:36 +04:00
|
|
|
*
|
|
|
|
* Fixes:
|
|
|
|
*
|
|
|
|
* Alan Cox : Added lots of __inline__ to optimise
|
|
|
|
* the memory usage of all the tiny little
|
|
|
|
* functions.
|
|
|
|
* Alan Cox : Dumped the header building experiment.
|
|
|
|
* Alan Cox : Minor tweaks ready for multicast routing
|
|
|
|
* and extended IGMP protocol.
|
|
|
|
* Alan Cox : Removed a load of inline directives. Gcc 2.5.8
|
|
|
|
* writes utterly bogus code otherwise (sigh)
|
|
|
|
* fixed IGMP loopback to behave in the manner
|
|
|
|
* desired by mrouted, fixed the fact it has been
|
|
|
|
* broken since 1.3.6 and cleaned up a few minor
|
|
|
|
* points.
|
|
|
|
*
|
|
|
|
* Chih-Jen Chang : Tried to revise IGMP to Version 2
|
|
|
|
* Tsu-Sheng Tsao E-mail: chihjenc@scf.usc.edu and tsusheng@scf.usc.edu
|
2007-02-09 17:24:47 +03:00
|
|
|
* The enhancements are mainly based on Steve Deering's
|
2005-04-17 02:20:36 +04:00
|
|
|
* ipmulti-3.5 source code.
|
|
|
|
* Chih-Jen Chang : Added the igmp_get_mrouter_info and
|
|
|
|
* Tsu-Sheng Tsao igmp_set_mrouter_info to keep track of
|
|
|
|
* the mrouted version on that device.
|
|
|
|
* Chih-Jen Chang : Added the max_resp_time parameter to
|
|
|
|
* Tsu-Sheng Tsao igmp_heard_query(). Using this parameter
|
|
|
|
* to identify the multicast router version
|
|
|
|
* and do what the IGMP version 2 specified.
|
|
|
|
* Chih-Jen Chang : Added a timer to revert to IGMP V2 router
|
|
|
|
* Tsu-Sheng Tsao if the specified time expired.
|
|
|
|
* Alan Cox : Stop IGMP from 0.0.0.0 being accepted.
|
|
|
|
* Alan Cox : Use GFP_ATOMIC in the right places.
|
|
|
|
* Christian Daudt : igmp timer wasn't set for local group
|
2007-02-09 17:24:47 +03:00
|
|
|
* memberships but was being deleted,
|
|
|
|
* which caused a "del_timer() called
|
2005-04-17 02:20:36 +04:00
|
|
|
* from %p with timer not initialized\n"
|
|
|
|
* message (960131).
|
2007-02-09 17:24:47 +03:00
|
|
|
* Christian Daudt : removed del_timer from
|
2005-04-17 02:20:36 +04:00
|
|
|
* igmp_timer_expire function (960205).
|
|
|
|
* Christian Daudt : igmp_heard_report now only calls
|
|
|
|
* igmp_timer_expire if tm->running is
|
|
|
|
* true (960216).
|
|
|
|
* Malcolm Beattie : ttl comparison wrong in igmp_rcv made
|
|
|
|
* igmp_heard_query never trigger. Expiry
|
|
|
|
* miscalculation fixed in igmp_heard_query
|
|
|
|
* and random() made to return unsigned to
|
|
|
|
* prevent negative expiry times.
|
|
|
|
* Alexey Kuznetsov: Wrong group leaving behaviour, backport
|
|
|
|
* fix from pending 2.1.x patches.
|
|
|
|
* Alan Cox: Forget to enable FDDI support earlier.
|
|
|
|
* Alexey Kuznetsov: Fixed leaving groups on device down.
|
|
|
|
* Alexey Kuznetsov: Accordance to igmp-v2-06 draft.
|
|
|
|
* David L Stevens: IGMPv3 support, with help from
|
|
|
|
* Vinay Kulkarni
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 11:04:11 +03:00
|
|
|
#include <linux/slab.h>
|
2016-12-24 22:46:01 +03:00
|
|
|
#include <linux/uaccess.h>
|
2005-04-17 02:20:36 +04:00
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/jiffies.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/socket.h>
|
|
|
|
#include <linux/sockios.h>
|
|
|
|
#include <linux/in.h>
|
|
|
|
#include <linux/inet.h>
|
|
|
|
#include <linux/netdevice.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/inetdevice.h>
|
|
|
|
#include <linux/igmp.h>
|
|
|
|
#include <linux/if_arp.h>
|
|
|
|
#include <linux/rtnetlink.h>
|
|
|
|
#include <linux/times.h>
|
2013-07-26 19:05:16 +04:00
|
|
|
#include <linux/pkt_sched.h>
|
2017-12-11 22:13:45 +03:00
|
|
|
#include <linux/byteorder/generic.h>
|
2005-12-27 07:43:12 +03:00
|
|
|
|
2007-09-12 14:01:34 +04:00
|
|
|
#include <net/net_namespace.h>
|
2005-12-27 07:43:12 +03:00
|
|
|
#include <net/arp.h>
|
2005-04-17 02:20:36 +04:00
|
|
|
#include <net/ip.h>
|
|
|
|
#include <net/protocol.h>
|
|
|
|
#include <net/route.h>
|
|
|
|
#include <net/sock.h>
|
|
|
|
#include <net/checksum.h>
|
2015-02-25 20:58:35 +03:00
|
|
|
#include <net/inet_common.h>
|
2005-04-17 02:20:36 +04:00
|
|
|
#include <linux/netfilter_ipv4.h>
|
|
|
|
#ifdef CONFIG_IP_MROUTE
|
|
|
|
#include <linux/mroute.h>
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
|
|
#include <linux/proc_fs.h>
|
|
|
|
#include <linux/seq_file.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
/* Parameter names and values are taken from igmp-v2-06 draft */
|
|
|
|
|
2018-10-26 06:30:35 +03:00
|
|
|
#define IGMP_QUERY_INTERVAL (125*HZ)
|
2014-11-04 22:52:14 +03:00
|
|
|
#define IGMP_QUERY_RESPONSE_INTERVAL (10*HZ)
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2014-11-04 22:52:14 +03:00
|
|
|
#define IGMP_INITIAL_REPORT_DELAY (1)
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2014-11-04 22:52:14 +03:00
|
|
|
/* IGMP_INITIAL_REPORT_DELAY is not from IGMP specs!
|
2005-04-17 02:20:36 +04:00
|
|
|
* IGMP specs require to report membership immediately after
|
|
|
|
* joining a group, but we delay the first report by a
|
|
|
|
* small interval. It seems more natural and still does not
|
|
|
|
* contradict to specs provided this delay is small enough.
|
|
|
|
*/
|
|
|
|
|
2007-06-05 10:34:44 +04:00
|
|
|
#define IGMP_V1_SEEN(in_dev) \
|
2008-03-25 15:47:49 +03:00
|
|
|
(IPV4_DEVCONF_ALL(dev_net(in_dev->dev), FORCE_IGMP_VERSION) == 1 || \
|
2007-06-05 10:34:44 +04:00
|
|
|
IN_DEV_CONF_GET((in_dev), FORCE_IGMP_VERSION) == 1 || \
|
|
|
|
((in_dev)->mr_v1_seen && \
|
|
|
|
time_before(jiffies, (in_dev)->mr_v1_seen)))
|
|
|
|
#define IGMP_V2_SEEN(in_dev) \
|
2008-03-25 15:47:49 +03:00
|
|
|
(IPV4_DEVCONF_ALL(dev_net(in_dev->dev), FORCE_IGMP_VERSION) == 2 || \
|
2007-06-05 10:34:44 +04:00
|
|
|
IN_DEV_CONF_GET((in_dev), FORCE_IGMP_VERSION) == 2 || \
|
|
|
|
((in_dev)->mr_v2_seen && \
|
|
|
|
time_before(jiffies, (in_dev)->mr_v2_seen)))
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2013-08-06 22:03:13 +04:00
|
|
|
static int unsolicited_report_interval(struct in_device *in_dev)
|
|
|
|
{
|
2013-08-06 22:03:15 +04:00
|
|
|
int interval_ms, interval_jiffies;
|
|
|
|
|
2013-08-06 22:03:13 +04:00
|
|
|
if (IGMP_V1_SEEN(in_dev) || IGMP_V2_SEEN(in_dev))
|
2013-08-06 22:03:15 +04:00
|
|
|
interval_ms = IN_DEV_CONF_GET(
|
|
|
|
in_dev,
|
|
|
|
IGMPV2_UNSOLICITED_REPORT_INTERVAL);
|
2013-08-06 22:03:13 +04:00
|
|
|
else /* v3 */
|
2013-08-06 22:03:15 +04:00
|
|
|
interval_ms = IN_DEV_CONF_GET(
|
|
|
|
in_dev,
|
|
|
|
IGMPV3_UNSOLICITED_REPORT_INTERVAL);
|
|
|
|
|
|
|
|
interval_jiffies = msecs_to_jiffies(interval_ms);
|
|
|
|
|
|
|
|
/* _timer functions can't handle a delay of 0 jiffies so ensure
|
|
|
|
* we always return a positive value.
|
|
|
|
*/
|
|
|
|
if (interval_jiffies <= 0)
|
|
|
|
interval_jiffies = 1;
|
|
|
|
return interval_jiffies;
|
2013-08-06 22:03:13 +04:00
|
|
|
}
|
|
|
|
|
2019-02-02 07:20:52 +03:00
|
|
|
static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im,
|
|
|
|
gfp_t gfp);
|
2016-11-14 11:16:28 +03:00
|
|
|
static void igmpv3_del_delrec(struct in_device *in_dev, struct ip_mc_list *im);
|
2005-04-17 02:20:36 +04:00
|
|
|
static void igmpv3_clear_delrec(struct in_device *in_dev);
|
|
|
|
static int sf_setstate(struct ip_mc_list *pmc);
|
|
|
|
static void sf_markstate(struct ip_mc_list *pmc);
|
|
|
|
#endif
|
|
|
|
static void ip_mc_clear_src(struct ip_mc_list *pmc);
|
2006-09-28 05:30:07 +04:00
|
|
|
static int ip_mc_add_src(struct in_device *in_dev, __be32 *pmca, int sfmode,
|
|
|
|
int sfcount, __be32 *psfsrc, int delta);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
static void ip_ma_put(struct ip_mc_list *im)
|
|
|
|
{
|
2017-06-30 13:08:02 +03:00
|
|
|
if (refcount_dec_and_test(&im->refcnt)) {
|
2005-04-17 02:20:36 +04:00
|
|
|
in_dev_put(im->interface);
|
2011-03-18 06:44:08 +03:00
|
|
|
kfree_rcu(im, rcu);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-11-15 19:52:02 +03:00
|
|
|
#define for_each_pmc_rcu(in_dev, pmc) \
|
|
|
|
for (pmc = rcu_dereference(in_dev->mc_list); \
|
|
|
|
pmc != NULL; \
|
|
|
|
pmc = rcu_dereference(pmc->next_rcu))
|
|
|
|
|
|
|
|
#define for_each_pmc_rtnl(in_dev, pmc) \
|
|
|
|
for (pmc = rtnl_dereference(in_dev->mc_list); \
|
|
|
|
pmc != NULL; \
|
|
|
|
pmc = rtnl_dereference(pmc->next_rcu))
|
|
|
|
|
2019-05-23 04:35:16 +03:00
|
|
|
static void ip_sf_list_clear_all(struct ip_sf_list *psf)
|
|
|
|
{
|
|
|
|
struct ip_sf_list *next;
|
|
|
|
|
|
|
|
while (psf) {
|
|
|
|
next = psf->sf_next;
|
|
|
|
kfree(psf);
|
|
|
|
psf = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Timer management
|
|
|
|
*/
|
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
static void igmp_stop_timer(struct ip_mc_list *im)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
spin_lock_bh(&im->lock);
|
|
|
|
if (del_timer(&im->timer))
|
2017-06-30 13:08:02 +03:00
|
|
|
refcount_dec(&im->refcnt);
|
2008-11-03 11:26:09 +03:00
|
|
|
im->tm_running = 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
im->reporter = 0;
|
|
|
|
im->unsolicit_count = 0;
|
|
|
|
spin_unlock_bh(&im->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* It must be called with locked im->lock */
|
|
|
|
static void igmp_start_timer(struct ip_mc_list *im, int max_delay)
|
|
|
|
{
|
2014-01-11 16:15:59 +04:00
|
|
|
int tv = prandom_u32() % max_delay;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2008-11-03 11:26:09 +03:00
|
|
|
im->tm_running = 1;
|
2023-11-23 10:13:14 +03:00
|
|
|
if (refcount_inc_not_zero(&im->refcnt)) {
|
|
|
|
if (mod_timer(&im->timer, jiffies + tv + 2))
|
|
|
|
ip_ma_put(im);
|
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void igmp_gq_start_timer(struct in_device *in_dev)
|
|
|
|
{
|
2014-01-11 16:15:59 +04:00
|
|
|
int tv = prandom_u32() % in_dev->mr_maxdelay;
|
igmp: Make igmp group member RFC 3376 compliant
5.2. Action on Reception of a Query
When a system receives a Query, it does not respond immediately.
Instead, it delays its response by a random amount of time, bounded
by the Max Resp Time value derived from the Max Resp Code in the
received Query message. A system may receive a variety of Queries on
different interfaces and of different kinds (e.g., General Queries,
Group-Specific Queries, and Group-and-Source-Specific Queries), each
of which may require its own delayed response.
Before scheduling a response to a Query, the system must first
consider previously scheduled pending responses and in many cases
schedule a combined response. Therefore, the system must be able to
maintain the following state:
o A timer per interface for scheduling responses to General Queries.
o A per-group and interface timer for scheduling responses to Group-
Specific and Group-and-Source-Specific Queries.
o A per-group and interface list of sources to be reported in the
response to a Group-and-Source-Specific Query.
When a new Query with the Router-Alert option arrives on an
interface, provided the system has state to report, a delay for a
response is randomly selected in the range (0, [Max Resp Time]) where
Max Resp Time is derived from Max Resp Code in the received Query
message. The following rules are then used to determine if a Report
needs to be scheduled and the type of Report to schedule. The rules
are considered in order and only the first matching rule is applied.
1. If there is a pending response to a previous General Query
scheduled sooner than the selected delay, no additional response
needs to be scheduled.
2. If the received Query is a General Query, the interface timer is
used to schedule a response to the General Query after the
selected delay. Any previously pending response to a General
Query is canceled.
--8<--
Currently the timer is rearmed with new random expiration time for
every incoming query regardless of possibly already pending report.
Which is not aligned with the above RFE.
It also might happen that higher rate of incoming queries can
postpone the report after the expiration time of the first query
causing group membership loss.
Now the per interface general query timer is rearmed only
when there is no pending report already scheduled on that interface or
the newly selected expiration time is before the already pending
scheduled report.
Signed-off-by: Michal Tesar <mtesar@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-01-02 16:38:36 +03:00
|
|
|
unsigned long exp = jiffies + tv + 2;
|
|
|
|
|
|
|
|
if (in_dev->mr_gq_running &&
|
|
|
|
time_after_eq(exp, (in_dev->mr_gq_timer).expires))
|
|
|
|
return;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
in_dev->mr_gq_running = 1;
|
igmp: Make igmp group member RFC 3376 compliant
5.2. Action on Reception of a Query
When a system receives a Query, it does not respond immediately.
Instead, it delays its response by a random amount of time, bounded
by the Max Resp Time value derived from the Max Resp Code in the
received Query message. A system may receive a variety of Queries on
different interfaces and of different kinds (e.g., General Queries,
Group-Specific Queries, and Group-and-Source-Specific Queries), each
of which may require its own delayed response.
Before scheduling a response to a Query, the system must first
consider previously scheduled pending responses and in many cases
schedule a combined response. Therefore, the system must be able to
maintain the following state:
o A timer per interface for scheduling responses to General Queries.
o A per-group and interface timer for scheduling responses to Group-
Specific and Group-and-Source-Specific Queries.
o A per-group and interface list of sources to be reported in the
response to a Group-and-Source-Specific Query.
When a new Query with the Router-Alert option arrives on an
interface, provided the system has state to report, a delay for a
response is randomly selected in the range (0, [Max Resp Time]) where
Max Resp Time is derived from Max Resp Code in the received Query
message. The following rules are then used to determine if a Report
needs to be scheduled and the type of Report to schedule. The rules
are considered in order and only the first matching rule is applied.
1. If there is a pending response to a previous General Query
scheduled sooner than the selected delay, no additional response
needs to be scheduled.
2. If the received Query is a General Query, the interface timer is
used to schedule a response to the General Query after the
selected delay. Any previously pending response to a General
Query is canceled.
--8<--
Currently the timer is rearmed with new random expiration time for
every incoming query regardless of possibly already pending report.
Which is not aligned with the above RFE.
It also might happen that higher rate of incoming queries can
postpone the report after the expiration time of the first query
causing group membership loss.
Now the per interface general query timer is rearmed only
when there is no pending report already scheduled on that interface or
the newly selected expiration time is before the already pending
scheduled report.
Signed-off-by: Michal Tesar <mtesar@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-01-02 16:38:36 +03:00
|
|
|
if (!mod_timer(&in_dev->mr_gq_timer, exp))
|
2005-04-17 02:20:36 +04:00
|
|
|
in_dev_hold(in_dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
|
|
|
|
{
|
2014-01-11 16:15:59 +04:00
|
|
|
int tv = prandom_u32() % delay;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (!mod_timer(&in_dev->mr_ifc_timer, jiffies+tv+2))
|
|
|
|
in_dev_hold(in_dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
|
|
|
|
{
|
|
|
|
spin_lock_bh(&im->lock);
|
|
|
|
im->unsolicit_count = 0;
|
|
|
|
if (del_timer(&im->timer)) {
|
|
|
|
if ((long)(im->timer.expires-jiffies) < max_delay) {
|
|
|
|
add_timer(&im->timer);
|
2008-11-03 11:26:09 +03:00
|
|
|
im->tm_running = 1;
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_unlock_bh(&im->lock);
|
|
|
|
return;
|
|
|
|
}
|
2017-06-30 13:08:02 +03:00
|
|
|
refcount_dec(&im->refcnt);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
igmp_start_timer(im, max_delay);
|
|
|
|
spin_unlock_bh(&im->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Send an IGMP report.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+4)
|
|
|
|
|
|
|
|
|
|
|
|
static int is_in(struct ip_mc_list *pmc, struct ip_sf_list *psf, int type,
|
|
|
|
int gdeleted, int sdeleted)
|
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
case IGMPV3_MODE_IS_INCLUDE:
|
|
|
|
case IGMPV3_MODE_IS_EXCLUDE:
|
|
|
|
if (gdeleted || sdeleted)
|
|
|
|
return 0;
|
2006-01-19 01:20:56 +03:00
|
|
|
if (!(pmc->gsquery && !psf->sf_gsresp)) {
|
|
|
|
if (pmc->sfmode == MCAST_INCLUDE)
|
|
|
|
return 1;
|
|
|
|
/* don't include if this source is excluded
|
|
|
|
* in all filters
|
|
|
|
*/
|
|
|
|
if (psf->sf_count[MCAST_INCLUDE])
|
|
|
|
return type == IGMPV3_MODE_IS_INCLUDE;
|
|
|
|
return pmc->sfcount[MCAST_EXCLUDE] ==
|
|
|
|
psf->sf_count[MCAST_EXCLUDE];
|
|
|
|
}
|
|
|
|
return 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
case IGMPV3_CHANGE_TO_INCLUDE:
|
|
|
|
if (gdeleted || sdeleted)
|
|
|
|
return 0;
|
|
|
|
return psf->sf_count[MCAST_INCLUDE] != 0;
|
|
|
|
case IGMPV3_CHANGE_TO_EXCLUDE:
|
|
|
|
if (gdeleted || sdeleted)
|
|
|
|
return 0;
|
|
|
|
if (pmc->sfcount[MCAST_EXCLUDE] == 0 ||
|
|
|
|
psf->sf_count[MCAST_INCLUDE])
|
|
|
|
return 0;
|
|
|
|
return pmc->sfcount[MCAST_EXCLUDE] ==
|
|
|
|
psf->sf_count[MCAST_EXCLUDE];
|
|
|
|
case IGMPV3_ALLOW_NEW_SOURCES:
|
|
|
|
if (gdeleted || !psf->sf_crcount)
|
|
|
|
return 0;
|
|
|
|
return (pmc->sfmode == MCAST_INCLUDE) ^ sdeleted;
|
|
|
|
case IGMPV3_BLOCK_OLD_SOURCES:
|
|
|
|
if (pmc->sfmode == MCAST_INCLUDE)
|
|
|
|
return gdeleted || (psf->sf_crcount && sdeleted);
|
|
|
|
return psf->sf_crcount && !gdeleted && !sdeleted;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
igmp_scount(struct ip_mc_list *pmc, int type, int gdeleted, int sdeleted)
|
|
|
|
{
|
|
|
|
struct ip_sf_list *psf;
|
|
|
|
int scount = 0;
|
|
|
|
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = pmc->sources; psf; psf = psf->sf_next) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!is_in(pmc, psf, type, gdeleted, sdeleted))
|
|
|
|
continue;
|
|
|
|
scount++;
|
|
|
|
}
|
|
|
|
return scount;
|
|
|
|
}
|
|
|
|
|
2017-12-11 22:13:45 +03:00
|
|
|
/* source address selection per RFC 3376 section 4.2.13 */
|
|
|
|
static __be32 igmpv3_get_srcaddr(struct net_device *dev,
|
|
|
|
const struct flowi4 *fl4)
|
|
|
|
{
|
|
|
|
struct in_device *in_dev = __in_dev_get_rcu(dev);
|
2019-05-31 19:27:07 +03:00
|
|
|
const struct in_ifaddr *ifa;
|
2017-12-11 22:13:45 +03:00
|
|
|
|
|
|
|
if (!in_dev)
|
|
|
|
return htonl(INADDR_ANY);
|
|
|
|
|
2019-05-31 19:27:07 +03:00
|
|
|
in_dev_for_each_ifa_rcu(ifa, in_dev) {
|
2018-01-19 13:50:46 +03:00
|
|
|
if (fl4->saddr == ifa->ifa_local)
|
2017-12-11 22:13:45 +03:00
|
|
|
return fl4->saddr;
|
2019-05-31 19:27:07 +03:00
|
|
|
}
|
2017-12-11 22:13:45 +03:00
|
|
|
|
|
|
|
return htonl(INADDR_ANY);
|
|
|
|
}
|
|
|
|
|
2014-11-05 22:27:38 +03:00
|
|
|
static struct sk_buff *igmpv3_newpack(struct net_device *dev, unsigned int mtu)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct rtable *rt;
|
|
|
|
struct iphdr *pip;
|
|
|
|
struct igmpv3_report *pig;
|
2008-08-14 03:15:57 +04:00
|
|
|
struct net *net = dev_net(dev);
|
2011-05-04 07:25:42 +04:00
|
|
|
struct flowi4 fl4;
|
2011-11-18 06:20:04 +04:00
|
|
|
int hlen = LL_RESERVED_SPACE(dev);
|
|
|
|
int tlen = dev->needed_tailroom;
|
2023-09-05 07:23:38 +03:00
|
|
|
unsigned int size;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2023-09-05 07:23:38 +03:00
|
|
|
size = min(mtu, IP_MAX_MTU);
|
2010-11-16 23:36:42 +03:00
|
|
|
while (1) {
|
2011-11-18 06:20:04 +04:00
|
|
|
skb = alloc_skb(size + hlen + tlen,
|
2010-11-16 23:36:42 +03:00
|
|
|
GFP_ATOMIC | __GFP_NOWARN);
|
|
|
|
if (skb)
|
|
|
|
break;
|
|
|
|
size >>= 1;
|
|
|
|
if (size < 256)
|
|
|
|
return NULL;
|
|
|
|
}
|
2013-07-26 19:05:16 +04:00
|
|
|
skb->priority = TC_PRIO_CONTROL;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2011-05-04 07:25:42 +04:00
|
|
|
rt = ip_route_output_ports(net, &fl4, NULL, IGMPV3_ALL_MCR, 0,
|
2011-03-12 08:00:52 +03:00
|
|
|
0, 0,
|
|
|
|
IPPROTO_IGMP, 0, dev->ifindex);
|
|
|
|
if (IS_ERR(rt)) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return NULL;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
2010-06-11 10:31:35 +04:00
|
|
|
skb_dst_set(skb, &rt->dst);
|
2005-04-17 02:20:36 +04:00
|
|
|
skb->dev = dev;
|
|
|
|
|
2011-11-18 06:20:04 +04:00
|
|
|
skb_reserve(skb, hlen);
|
mld, igmp: Fix reserved tailroom calculation
The current reserved_tailroom calculation fails to take hlen and tlen into
account.
skb:
[__hlen__|__data____________|__tlen___|__extra__]
^ ^
head skb_end_offset
In this representation, hlen + data + tlen is the size passed to alloc_skb.
"extra" is the extra space made available in __alloc_skb because of
rounding up by kmalloc. We can reorder the representation like so:
[__hlen__|__data____________|__extra__|__tlen___]
^ ^
head skb_end_offset
The maximum space available for ip headers and payload without
fragmentation is min(mtu, data + extra). Therefore,
reserved_tailroom
= data + extra + tlen - min(mtu, data + extra)
= skb_end_offset - hlen - min(mtu, skb_end_offset - hlen - tlen)
= skb_tailroom - min(mtu, skb_tailroom - tlen) ; after skb_reserve(hlen)
Compare the second line to the current expression:
reserved_tailroom = skb_end_offset - min(mtu, skb_end_offset)
and we can see that hlen and tlen are not taken into account.
The min() in the third line can be expanded into:
if mtu < skb_tailroom - tlen:
reserved_tailroom = skb_tailroom - mtu
else:
reserved_tailroom = tlen
Depending on hlen, tlen, mtu and the number of multicast address records,
the current code may output skbs that have less tailroom than
dev->needed_tailroom or it may output more skbs than needed because not all
space available is used.
Fixes: 4c672e4b ("ipv6: mld: fix add_grhead skb_over_panic for devs with large MTUs")
Signed-off-by: Benjamin Poirier <bpoirier@suse.com>
Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-03-01 02:03:33 +03:00
|
|
|
skb_tailroom_reserve(skb, mtu, tlen);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2007-03-11 00:40:59 +03:00
|
|
|
skb_reset_network_header(skb);
|
2007-04-21 09:47:35 +04:00
|
|
|
pip = ip_hdr(skb);
|
2007-03-11 00:40:59 +03:00
|
|
|
skb_put(skb, sizeof(struct iphdr) + 4);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
pip->version = 4;
|
|
|
|
pip->ihl = (sizeof(struct iphdr)+4)>>2;
|
|
|
|
pip->tos = 0xc0;
|
|
|
|
pip->frag_off = htons(IP_DF);
|
|
|
|
pip->ttl = 1;
|
2011-05-04 07:53:12 +04:00
|
|
|
pip->daddr = fl4.daddr;
|
2018-02-01 21:26:57 +03:00
|
|
|
|
|
|
|
rcu_read_lock();
|
2017-12-11 22:13:45 +03:00
|
|
|
pip->saddr = igmpv3_get_srcaddr(dev, &fl4);
|
2018-02-01 21:26:57 +03:00
|
|
|
rcu_read_unlock();
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
pip->protocol = IPPROTO_IGMP;
|
|
|
|
pip->tot_len = 0; /* filled in later */
|
2015-03-25 19:07:44 +03:00
|
|
|
ip_select_ident(net, skb, NULL);
|
2012-04-15 05:34:41 +04:00
|
|
|
((u8 *)&pip[1])[0] = IPOPT_RA;
|
|
|
|
((u8 *)&pip[1])[1] = 4;
|
|
|
|
((u8 *)&pip[1])[2] = 0;
|
|
|
|
((u8 *)&pip[1])[3] = 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2007-04-11 08:21:55 +04:00
|
|
|
skb->transport_header = skb->network_header + sizeof(struct iphdr) + 4;
|
2007-03-15 03:05:37 +03:00
|
|
|
skb_put(skb, sizeof(*pig));
|
2007-03-13 20:19:23 +03:00
|
|
|
pig = igmpv3_report_hdr(skb);
|
2005-04-17 02:20:36 +04:00
|
|
|
pig->type = IGMPV3_HOST_MEMBERSHIP_REPORT;
|
|
|
|
pig->resv1 = 0;
|
|
|
|
pig->csum = 0;
|
|
|
|
pig->resv2 = 0;
|
|
|
|
pig->ngrec = 0;
|
|
|
|
return skb;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int igmpv3_sendpack(struct sk_buff *skb)
|
|
|
|
{
|
2007-03-13 20:19:23 +03:00
|
|
|
struct igmphdr *pig = igmp_hdr(skb);
|
2013-05-29 00:34:27 +04:00
|
|
|
const int igmplen = skb_tail_pointer(skb) - skb_transport_header(skb);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2007-03-13 20:19:23 +03:00
|
|
|
pig->csum = ip_compute_csum(igmp_hdr(skb), igmplen);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2015-10-08 00:48:46 +03:00
|
|
|
return ip_local_out(dev_net(skb_dst(skb)->dev), skb->sk, skb);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int grec_size(struct ip_mc_list *pmc, int type, int gdel, int sdel)
|
|
|
|
{
|
2008-11-03 11:26:09 +03:00
|
|
|
return sizeof(struct igmpv3_grec) + 4*igmp_scount(pmc, type, gdel, sdel);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct sk_buff *add_grhead(struct sk_buff *skb, struct ip_mc_list *pmc,
|
2017-12-11 18:17:39 +03:00
|
|
|
int type, struct igmpv3_grec **ppgr, unsigned int mtu)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct net_device *dev = pmc->interface->dev;
|
|
|
|
struct igmpv3_report *pih;
|
|
|
|
struct igmpv3_grec *pgr;
|
|
|
|
|
2017-12-11 18:17:39 +03:00
|
|
|
if (!skb) {
|
|
|
|
skb = igmpv3_newpack(dev, mtu);
|
|
|
|
if (!skb)
|
|
|
|
return NULL;
|
|
|
|
}
|
networking: make skb_put & friends return void pointers
It seems like a historic accident that these return unsigned char *,
and in many places that means casts are required, more often than not.
Make these functions (skb_put, __skb_put and pskb_put) return void *
and remove all the casts across the tree, adding a (u8 *) cast only
where the unsigned char pointer was used directly, all done with the
following spatch:
@@
expression SKB, LEN;
typedef u8;
identifier fn = { skb_put, __skb_put };
@@
- *(fn(SKB, LEN))
+ *(u8 *)fn(SKB, LEN)
@@
expression E, SKB, LEN;
identifier fn = { skb_put, __skb_put };
type T;
@@
- E = ((T *)(fn(SKB, LEN)))
+ E = fn(SKB, LEN)
which actually doesn't cover pskb_put since there are only three
users overall.
A handful of stragglers were converted manually, notably a macro in
drivers/isdn/i4l/isdn_bsdcomp.c and, oddly enough, one of the many
instances in net/bluetooth/hci_sock.c. In the former file, I also
had to fix one whitespace problem spatch introduced.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 15:29:21 +03:00
|
|
|
pgr = skb_put(skb, sizeof(struct igmpv3_grec));
|
2005-04-17 02:20:36 +04:00
|
|
|
pgr->grec_type = type;
|
|
|
|
pgr->grec_auxwords = 0;
|
|
|
|
pgr->grec_nsrcs = 0;
|
|
|
|
pgr->grec_mca = pmc->multiaddr;
|
2007-03-13 20:19:23 +03:00
|
|
|
pih = igmpv3_report_hdr(skb);
|
2005-04-17 02:20:36 +04:00
|
|
|
pih->ngrec = htons(ntohs(pih->ngrec)+1);
|
|
|
|
*ppgr = pgr;
|
|
|
|
return skb;
|
|
|
|
}
|
|
|
|
|
2014-11-05 22:27:38 +03:00
|
|
|
#define AVAILABLE(skb) ((skb) ? skb_availroom(skb) : 0)
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc,
|
|
|
|
int type, int gdeleted, int sdeleted)
|
|
|
|
{
|
|
|
|
struct net_device *dev = pmc->interface->dev;
|
2016-02-09 01:13:50 +03:00
|
|
|
struct net *net = dev_net(dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
struct igmpv3_report *pih;
|
|
|
|
struct igmpv3_grec *pgr = NULL;
|
|
|
|
struct ip_sf_list *psf, *psf_next, *psf_prev, **psf_list;
|
2006-01-19 01:20:56 +03:00
|
|
|
int scount, stotal, first, isquery, truncate;
|
2017-12-11 18:17:39 +03:00
|
|
|
unsigned int mtu;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (pmc->multiaddr == IGMP_ALL_HOSTS)
|
|
|
|
return skb;
|
2022-07-15 20:17:41 +03:00
|
|
|
if (ipv4_is_local_multicast(pmc->multiaddr) &&
|
|
|
|
!READ_ONCE(net->ipv4.sysctl_igmp_llm_reports))
|
2015-08-27 18:46:26 +03:00
|
|
|
return skb;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2017-12-11 18:17:39 +03:00
|
|
|
mtu = READ_ONCE(dev->mtu);
|
|
|
|
if (mtu < IPV4_MIN_MTU)
|
|
|
|
return skb;
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
isquery = type == IGMPV3_MODE_IS_INCLUDE ||
|
|
|
|
type == IGMPV3_MODE_IS_EXCLUDE;
|
|
|
|
truncate = type == IGMPV3_MODE_IS_EXCLUDE ||
|
|
|
|
type == IGMPV3_CHANGE_TO_EXCLUDE;
|
|
|
|
|
2006-01-19 01:20:56 +03:00
|
|
|
stotal = scount = 0;
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
psf_list = sdeleted ? &pmc->tomb : &pmc->sources;
|
|
|
|
|
2006-01-19 01:20:56 +03:00
|
|
|
if (!*psf_list)
|
|
|
|
goto empty_source;
|
|
|
|
|
2007-03-13 20:19:23 +03:00
|
|
|
pih = skb ? igmpv3_report_hdr(skb) : NULL;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
/* EX and TO_EX get a fresh packet, if needed */
|
|
|
|
if (truncate) {
|
|
|
|
if (pih && pih->ngrec &&
|
|
|
|
AVAILABLE(skb) < grec_size(pmc, type, gdeleted, sdeleted)) {
|
|
|
|
if (skb)
|
|
|
|
igmpv3_sendpack(skb);
|
2017-12-11 18:17:39 +03:00
|
|
|
skb = igmpv3_newpack(dev, mtu);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
first = 1;
|
|
|
|
psf_prev = NULL;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = *psf_list; psf; psf = psf_next) {
|
2006-09-28 05:30:52 +04:00
|
|
|
__be32 *psrc;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
psf_next = psf->sf_next;
|
|
|
|
|
|
|
|
if (!is_in(pmc, psf, type, gdeleted, sdeleted)) {
|
|
|
|
psf_prev = psf;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-08-02 13:02:57 +03:00
|
|
|
/* Based on RFC3376 5.1. Should not send source-list change
|
|
|
|
* records when there is a filter mode change.
|
|
|
|
*/
|
|
|
|
if (((gdeleted && pmc->sfmode == MCAST_EXCLUDE) ||
|
|
|
|
(!gdeleted && pmc->crcount)) &&
|
|
|
|
(type == IGMPV3_ALLOW_NEW_SOURCES ||
|
|
|
|
type == IGMPV3_BLOCK_OLD_SOURCES) && psf->sf_crcount)
|
|
|
|
goto decrease_sf_crcount;
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
/* clear marks on query responses */
|
|
|
|
if (isquery)
|
|
|
|
psf->sf_gsresp = 0;
|
|
|
|
|
2006-09-28 05:31:32 +04:00
|
|
|
if (AVAILABLE(skb) < sizeof(__be32) +
|
2005-04-17 02:20:36 +04:00
|
|
|
first*sizeof(struct igmpv3_grec)) {
|
|
|
|
if (truncate && !first)
|
|
|
|
break; /* truncate these */
|
|
|
|
if (pgr)
|
|
|
|
pgr->grec_nsrcs = htons(scount);
|
|
|
|
if (skb)
|
|
|
|
igmpv3_sendpack(skb);
|
2017-12-11 18:17:39 +03:00
|
|
|
skb = igmpv3_newpack(dev, mtu);
|
2005-04-17 02:20:36 +04:00
|
|
|
first = 1;
|
|
|
|
scount = 0;
|
|
|
|
}
|
|
|
|
if (first) {
|
2017-12-11 18:17:39 +03:00
|
|
|
skb = add_grhead(skb, pmc, type, &pgr, mtu);
|
2005-04-17 02:20:36 +04:00
|
|
|
first = 0;
|
|
|
|
}
|
2007-02-07 01:35:25 +03:00
|
|
|
if (!skb)
|
|
|
|
return NULL;
|
networking: make skb_put & friends return void pointers
It seems like a historic accident that these return unsigned char *,
and in many places that means casts are required, more often than not.
Make these functions (skb_put, __skb_put and pskb_put) return void *
and remove all the casts across the tree, adding a (u8 *) cast only
where the unsigned char pointer was used directly, all done with the
following spatch:
@@
expression SKB, LEN;
typedef u8;
identifier fn = { skb_put, __skb_put };
@@
- *(fn(SKB, LEN))
+ *(u8 *)fn(SKB, LEN)
@@
expression E, SKB, LEN;
identifier fn = { skb_put, __skb_put };
type T;
@@
- E = ((T *)(fn(SKB, LEN)))
+ E = fn(SKB, LEN)
which actually doesn't cover pskb_put since there are only three
users overall.
A handful of stragglers were converted manually, notably a macro in
drivers/isdn/i4l/isdn_bsdcomp.c and, oddly enough, one of the many
instances in net/bluetooth/hci_sock.c. In the former file, I also
had to fix one whitespace problem spatch introduced.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 15:29:21 +03:00
|
|
|
psrc = skb_put(skb, sizeof(__be32));
|
2005-04-17 02:20:36 +04:00
|
|
|
*psrc = psf->sf_inaddr;
|
2006-01-19 01:20:56 +03:00
|
|
|
scount++; stotal++;
|
2005-04-17 02:20:36 +04:00
|
|
|
if ((type == IGMPV3_ALLOW_NEW_SOURCES ||
|
|
|
|
type == IGMPV3_BLOCK_OLD_SOURCES) && psf->sf_crcount) {
|
2016-08-02 13:02:57 +03:00
|
|
|
decrease_sf_crcount:
|
2005-04-17 02:20:36 +04:00
|
|
|
psf->sf_crcount--;
|
|
|
|
if ((sdeleted || gdeleted) && psf->sf_crcount == 0) {
|
|
|
|
if (psf_prev)
|
|
|
|
psf_prev->sf_next = psf->sf_next;
|
|
|
|
else
|
|
|
|
*psf_list = psf->sf_next;
|
|
|
|
kfree(psf);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
psf_prev = psf;
|
|
|
|
}
|
2006-01-19 01:20:56 +03:00
|
|
|
|
|
|
|
empty_source:
|
|
|
|
if (!stotal) {
|
|
|
|
if (type == IGMPV3_ALLOW_NEW_SOURCES ||
|
|
|
|
type == IGMPV3_BLOCK_OLD_SOURCES)
|
|
|
|
return skb;
|
|
|
|
if (pmc->crcount || isquery) {
|
|
|
|
/* make sure we have room for group header */
|
2013-12-23 10:37:29 +04:00
|
|
|
if (skb && AVAILABLE(skb) < sizeof(struct igmpv3_grec)) {
|
2006-01-19 01:20:56 +03:00
|
|
|
igmpv3_sendpack(skb);
|
|
|
|
skb = NULL; /* add_grhead will get a new one */
|
|
|
|
}
|
2017-12-11 18:17:39 +03:00
|
|
|
skb = add_grhead(skb, pmc, type, &pgr, mtu);
|
2006-01-19 01:20:56 +03:00
|
|
|
}
|
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pgr)
|
|
|
|
pgr->grec_nsrcs = htons(scount);
|
|
|
|
|
|
|
|
if (isquery)
|
|
|
|
pmc->gsquery = 0; /* clear query state on report */
|
|
|
|
return skb;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int igmpv3_send_report(struct in_device *in_dev, struct ip_mc_list *pmc)
|
|
|
|
{
|
|
|
|
struct sk_buff *skb = NULL;
|
2016-02-09 01:13:50 +03:00
|
|
|
struct net *net = dev_net(in_dev->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
int type;
|
|
|
|
|
|
|
|
if (!pmc) {
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_lock();
|
|
|
|
for_each_pmc_rcu(in_dev, pmc) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc->multiaddr == IGMP_ALL_HOSTS)
|
|
|
|
continue;
|
2015-08-27 18:46:26 +03:00
|
|
|
if (ipv4_is_local_multicast(pmc->multiaddr) &&
|
2022-07-15 20:17:41 +03:00
|
|
|
!READ_ONCE(net->ipv4.sysctl_igmp_llm_reports))
|
2015-08-27 18:46:26 +03:00
|
|
|
continue;
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock_bh(&pmc->lock);
|
|
|
|
if (pmc->sfcount[MCAST_EXCLUDE])
|
|
|
|
type = IGMPV3_MODE_IS_EXCLUDE;
|
|
|
|
else
|
|
|
|
type = IGMPV3_MODE_IS_INCLUDE;
|
|
|
|
skb = add_grec(skb, pmc, type, 0, 0);
|
|
|
|
spin_unlock_bh(&pmc->lock);
|
|
|
|
}
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_unlock();
|
2005-04-17 02:20:36 +04:00
|
|
|
} else {
|
|
|
|
spin_lock_bh(&pmc->lock);
|
|
|
|
if (pmc->sfcount[MCAST_EXCLUDE])
|
|
|
|
type = IGMPV3_MODE_IS_EXCLUDE;
|
|
|
|
else
|
|
|
|
type = IGMPV3_MODE_IS_INCLUDE;
|
|
|
|
skb = add_grec(skb, pmc, type, 0, 0);
|
|
|
|
spin_unlock_bh(&pmc->lock);
|
|
|
|
}
|
|
|
|
if (!skb)
|
|
|
|
return 0;
|
|
|
|
return igmpv3_sendpack(skb);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* remove zero-count source records from a source filter list
|
|
|
|
*/
|
|
|
|
static void igmpv3_clear_zeros(struct ip_sf_list **ppsf)
|
|
|
|
{
|
|
|
|
struct ip_sf_list *psf_prev, *psf_next, *psf;
|
|
|
|
|
|
|
|
psf_prev = NULL;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = *ppsf; psf; psf = psf_next) {
|
2005-04-17 02:20:36 +04:00
|
|
|
psf_next = psf->sf_next;
|
|
|
|
if (psf->sf_crcount == 0) {
|
|
|
|
if (psf_prev)
|
|
|
|
psf_prev->sf_next = psf->sf_next;
|
|
|
|
else
|
|
|
|
*ppsf = psf->sf_next;
|
|
|
|
kfree(psf);
|
|
|
|
} else
|
|
|
|
psf_prev = psf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-23 02:51:22 +03:00
|
|
|
static void kfree_pmc(struct ip_mc_list *pmc)
|
|
|
|
{
|
|
|
|
ip_sf_list_clear_all(pmc->sources);
|
|
|
|
ip_sf_list_clear_all(pmc->tomb);
|
|
|
|
kfree(pmc);
|
|
|
|
}
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
static void igmpv3_send_cr(struct in_device *in_dev)
|
|
|
|
{
|
|
|
|
struct ip_mc_list *pmc, *pmc_prev, *pmc_next;
|
|
|
|
struct sk_buff *skb = NULL;
|
|
|
|
int type, dtype;
|
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_lock();
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock_bh(&in_dev->mc_tomb_lock);
|
|
|
|
|
|
|
|
/* deleted MCA's */
|
|
|
|
pmc_prev = NULL;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (pmc = in_dev->mc_tomb; pmc; pmc = pmc_next) {
|
2005-04-17 02:20:36 +04:00
|
|
|
pmc_next = pmc->next;
|
|
|
|
if (pmc->sfmode == MCAST_INCLUDE) {
|
|
|
|
type = IGMPV3_BLOCK_OLD_SOURCES;
|
|
|
|
dtype = IGMPV3_BLOCK_OLD_SOURCES;
|
|
|
|
skb = add_grec(skb, pmc, type, 1, 0);
|
|
|
|
skb = add_grec(skb, pmc, dtype, 1, 1);
|
|
|
|
}
|
|
|
|
if (pmc->crcount) {
|
|
|
|
if (pmc->sfmode == MCAST_EXCLUDE) {
|
|
|
|
type = IGMPV3_CHANGE_TO_INCLUDE;
|
|
|
|
skb = add_grec(skb, pmc, type, 1, 0);
|
|
|
|
}
|
2006-01-19 01:20:56 +03:00
|
|
|
pmc->crcount--;
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc->crcount == 0) {
|
|
|
|
igmpv3_clear_zeros(&pmc->tomb);
|
|
|
|
igmpv3_clear_zeros(&pmc->sources);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pmc->crcount == 0 && !pmc->tomb && !pmc->sources) {
|
|
|
|
if (pmc_prev)
|
|
|
|
pmc_prev->next = pmc_next;
|
|
|
|
else
|
|
|
|
in_dev->mc_tomb = pmc_next;
|
|
|
|
in_dev_put(pmc->interface);
|
2019-05-23 02:51:22 +03:00
|
|
|
kfree_pmc(pmc);
|
2005-04-17 02:20:36 +04:00
|
|
|
} else
|
|
|
|
pmc_prev = pmc;
|
|
|
|
}
|
|
|
|
spin_unlock_bh(&in_dev->mc_tomb_lock);
|
|
|
|
|
|
|
|
/* change recs */
|
2010-11-12 08:46:50 +03:00
|
|
|
for_each_pmc_rcu(in_dev, pmc) {
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock_bh(&pmc->lock);
|
|
|
|
if (pmc->sfcount[MCAST_EXCLUDE]) {
|
|
|
|
type = IGMPV3_BLOCK_OLD_SOURCES;
|
|
|
|
dtype = IGMPV3_ALLOW_NEW_SOURCES;
|
|
|
|
} else {
|
|
|
|
type = IGMPV3_ALLOW_NEW_SOURCES;
|
|
|
|
dtype = IGMPV3_BLOCK_OLD_SOURCES;
|
|
|
|
}
|
|
|
|
skb = add_grec(skb, pmc, type, 0, 0);
|
|
|
|
skb = add_grec(skb, pmc, dtype, 0, 1); /* deleted sources */
|
|
|
|
|
|
|
|
/* filter mode changes */
|
|
|
|
if (pmc->crcount) {
|
|
|
|
if (pmc->sfmode == MCAST_EXCLUDE)
|
|
|
|
type = IGMPV3_CHANGE_TO_EXCLUDE;
|
|
|
|
else
|
|
|
|
type = IGMPV3_CHANGE_TO_INCLUDE;
|
|
|
|
skb = add_grec(skb, pmc, type, 0, 0);
|
2006-01-19 01:20:56 +03:00
|
|
|
pmc->crcount--;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
spin_unlock_bh(&pmc->lock);
|
|
|
|
}
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_unlock();
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (!skb)
|
|
|
|
return;
|
|
|
|
(void) igmpv3_sendpack(skb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int igmp_send_report(struct in_device *in_dev, struct ip_mc_list *pmc,
|
|
|
|
int type)
|
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct iphdr *iph;
|
|
|
|
struct igmphdr *ih;
|
|
|
|
struct rtable *rt;
|
|
|
|
struct net_device *dev = in_dev->dev;
|
2008-08-14 03:15:57 +04:00
|
|
|
struct net *net = dev_net(dev);
|
2006-09-28 05:31:32 +04:00
|
|
|
__be32 group = pmc ? pmc->multiaddr : 0;
|
2011-05-04 07:25:42 +04:00
|
|
|
struct flowi4 fl4;
|
2006-09-28 05:31:32 +04:00
|
|
|
__be32 dst;
|
2011-11-18 06:20:04 +04:00
|
|
|
int hlen, tlen;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (type == IGMPV3_HOST_MEMBERSHIP_REPORT)
|
|
|
|
return igmpv3_send_report(in_dev, pmc);
|
2015-08-27 18:46:26 +03:00
|
|
|
|
2022-07-15 20:17:41 +03:00
|
|
|
if (ipv4_is_local_multicast(group) &&
|
|
|
|
!READ_ONCE(net->ipv4.sysctl_igmp_llm_reports))
|
2015-08-27 18:46:26 +03:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (type == IGMP_HOST_LEAVE_MESSAGE)
|
2005-04-17 02:20:36 +04:00
|
|
|
dst = IGMP_ALL_ROUTER;
|
|
|
|
else
|
|
|
|
dst = group;
|
|
|
|
|
2011-05-04 07:25:42 +04:00
|
|
|
rt = ip_route_output_ports(net, &fl4, NULL, dst, 0,
|
2011-03-12 08:00:52 +03:00
|
|
|
0, 0,
|
|
|
|
IPPROTO_IGMP, 0, dev->ifindex);
|
|
|
|
if (IS_ERR(rt))
|
|
|
|
return -1;
|
|
|
|
|
2011-11-18 06:20:04 +04:00
|
|
|
hlen = LL_RESERVED_SPACE(dev);
|
|
|
|
tlen = dev->needed_tailroom;
|
|
|
|
skb = alloc_skb(IGMP_SIZE + hlen + tlen, GFP_ATOMIC);
|
2015-04-03 11:17:26 +03:00
|
|
|
if (!skb) {
|
2005-04-17 02:20:36 +04:00
|
|
|
ip_rt_put(rt);
|
|
|
|
return -1;
|
|
|
|
}
|
2013-07-26 19:05:16 +04:00
|
|
|
skb->priority = TC_PRIO_CONTROL;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2010-06-11 10:31:35 +04:00
|
|
|
skb_dst_set(skb, &rt->dst);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2011-11-18 06:20:04 +04:00
|
|
|
skb_reserve(skb, hlen);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2007-03-11 00:40:59 +03:00
|
|
|
skb_reset_network_header(skb);
|
2007-04-21 09:47:35 +04:00
|
|
|
iph = ip_hdr(skb);
|
2007-03-11 00:40:59 +03:00
|
|
|
skb_put(skb, sizeof(struct iphdr) + 4);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
iph->version = 4;
|
|
|
|
iph->ihl = (sizeof(struct iphdr)+4)>>2;
|
|
|
|
iph->tos = 0xc0;
|
|
|
|
iph->frag_off = htons(IP_DF);
|
|
|
|
iph->ttl = 1;
|
|
|
|
iph->daddr = dst;
|
2011-05-04 07:53:12 +04:00
|
|
|
iph->saddr = fl4.saddr;
|
2005-04-17 02:20:36 +04:00
|
|
|
iph->protocol = IPPROTO_IGMP;
|
2015-03-25 19:07:44 +03:00
|
|
|
ip_select_ident(net, skb, NULL);
|
2012-04-15 05:34:41 +04:00
|
|
|
((u8 *)&iph[1])[0] = IPOPT_RA;
|
|
|
|
((u8 *)&iph[1])[1] = 4;
|
|
|
|
((u8 *)&iph[1])[2] = 0;
|
|
|
|
((u8 *)&iph[1])[3] = 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
networking: make skb_put & friends return void pointers
It seems like a historic accident that these return unsigned char *,
and in many places that means casts are required, more often than not.
Make these functions (skb_put, __skb_put and pskb_put) return void *
and remove all the casts across the tree, adding a (u8 *) cast only
where the unsigned char pointer was used directly, all done with the
following spatch:
@@
expression SKB, LEN;
typedef u8;
identifier fn = { skb_put, __skb_put };
@@
- *(fn(SKB, LEN))
+ *(u8 *)fn(SKB, LEN)
@@
expression E, SKB, LEN;
identifier fn = { skb_put, __skb_put };
type T;
@@
- E = ((T *)(fn(SKB, LEN)))
+ E = fn(SKB, LEN)
which actually doesn't cover pskb_put since there are only three
users overall.
A handful of stragglers were converted manually, notably a macro in
drivers/isdn/i4l/isdn_bsdcomp.c and, oddly enough, one of the many
instances in net/bluetooth/hci_sock.c. In the former file, I also
had to fix one whitespace problem spatch introduced.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 15:29:21 +03:00
|
|
|
ih = skb_put(skb, sizeof(struct igmphdr));
|
2008-11-03 11:26:09 +03:00
|
|
|
ih->type = type;
|
|
|
|
ih->code = 0;
|
|
|
|
ih->csum = 0;
|
|
|
|
ih->group = group;
|
|
|
|
ih->csum = ip_compute_csum((void *)ih, sizeof(struct igmphdr));
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2015-10-08 00:48:46 +03:00
|
|
|
return ip_local_out(net, skb->sk, skb);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
treewide: setup_timer() -> timer_setup()
This converts all remaining cases of the old setup_timer() API into using
timer_setup(), where the callback argument is the structure already
holding the struct timer_list. These should have no behavioral changes,
since they just change which pointer is passed into the callback with
the same available pointers after conversion. It handles the following
examples, in addition to some other variations.
Casting from unsigned long:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
setup_timer(&ptr->my_timer, my_callback, ptr);
and forced object casts:
void my_callback(struct something *ptr)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr);
become:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
Direct function assignments:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
ptr->my_timer.function = my_callback;
have a temporary cast added, along with converting the args:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback;
And finally, callbacks without a data assignment:
void my_callback(unsigned long data)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, 0);
have their argument renamed to verify they're unused during conversion:
void my_callback(struct timer_list *unused)
{
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
The conversion is done with the following Coccinelle script:
spatch --very-quiet --all-includes --include-headers \
-I ./arch/x86/include -I ./arch/x86/include/generated \
-I ./include -I ./arch/x86/include/uapi \
-I ./arch/x86/include/generated/uapi -I ./include/uapi \
-I ./include/generated/uapi --include ./include/linux/kconfig.h \
--dir . \
--cocci-file ~/src/data/timer_setup.cocci
@fix_address_of@
expression e;
@@
setup_timer(
-&(e)
+&e
, ...)
// Update any raw setup_timer() usages that have a NULL callback, but
// would otherwise match change_timer_function_usage, since the latter
// will update all function assignments done in the face of a NULL
// function initialization in setup_timer().
@change_timer_function_usage_NULL@
expression _E;
identifier _timer;
type _cast_data;
@@
(
-setup_timer(&_E->_timer, NULL, _E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E->_timer, NULL, (_cast_data)_E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, &_E);
+timer_setup(&_E._timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, (_cast_data)&_E);
+timer_setup(&_E._timer, NULL, 0);
)
@change_timer_function_usage@
expression _E;
identifier _timer;
struct timer_list _stl;
identifier _callback;
type _cast_func, _cast_data;
@@
(
-setup_timer(&_E->_timer, _callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
_E->_timer@_stl.function = _callback;
|
_E->_timer@_stl.function = &_callback;
|
_E->_timer@_stl.function = (_cast_func)_callback;
|
_E->_timer@_stl.function = (_cast_func)&_callback;
|
_E._timer@_stl.function = _callback;
|
_E._timer@_stl.function = &_callback;
|
_E._timer@_stl.function = (_cast_func)_callback;
|
_E._timer@_stl.function = (_cast_func)&_callback;
)
// callback(unsigned long arg)
@change_callback_handle_cast
depends on change_timer_function_usage@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
identifier _handle;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
(
... when != _origarg
_handletype *_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
)
}
// callback(unsigned long arg) without existing variable
@change_callback_handle_cast_no_arg
depends on change_timer_function_usage &&
!change_callback_handle_cast@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
+ _handletype *_origarg = from_timer(_origarg, t, _timer);
+
... when != _origarg
- (_handletype *)_origarg
+ _origarg
... when != _origarg
}
// Avoid already converted callbacks.
@match_callback_converted
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier t;
@@
void _callback(struct timer_list *t)
{ ... }
// callback(struct something *handle)
@change_callback_handle_arg
depends on change_timer_function_usage &&
!match_callback_converted &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
@@
void _callback(
-_handletype *_handle
+struct timer_list *t
)
{
+ _handletype *_handle = from_timer(_handle, t, _timer);
...
}
// If change_callback_handle_arg ran on an empty function, remove
// the added handler.
@unchange_callback_handle_arg
depends on change_timer_function_usage &&
change_callback_handle_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
identifier t;
@@
void _callback(struct timer_list *t)
{
- _handletype *_handle = from_timer(_handle, t, _timer);
}
// We only want to refactor the setup_timer() data argument if we've found
// the matching callback. This undoes changes in change_timer_function_usage.
@unchange_timer_function_usage
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg &&
!change_callback_handle_arg@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type change_timer_function_usage._cast_data;
@@
(
-timer_setup(&_E->_timer, _callback, 0);
+setup_timer(&_E->_timer, _callback, (_cast_data)_E);
|
-timer_setup(&_E._timer, _callback, 0);
+setup_timer(&_E._timer, _callback, (_cast_data)&_E);
)
// If we fixed a callback from a .function assignment, fix the
// assignment cast now.
@change_timer_function_assignment
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_func;
typedef TIMER_FUNC_TYPE;
@@
(
_E->_timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-&_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
)
// Sometimes timer functions are called directly. Replace matched args.
@change_timer_function_calls
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression _E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_data;
@@
_callback(
(
-(_cast_data)_E
+&_E->_timer
|
-(_cast_data)&_E
+&_E._timer
|
-_E
+&_E->_timer
)
)
// If a timer has been configured without a data argument, it can be
// converted without regard to the callback argument, since it is unused.
@match_timer_function_unused_data@
expression _E;
identifier _timer;
identifier _callback;
@@
(
-setup_timer(&_E->_timer, _callback, 0);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0L);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0UL);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0L);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0UL);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0L);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0UL);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0L);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0UL);
+timer_setup(_timer, _callback, 0);
)
@change_callback_unused_data
depends on match_timer_function_unused_data@
identifier match_timer_function_unused_data._callback;
type _origtype;
identifier _origarg;
@@
void _callback(
-_origtype _origarg
+struct timer_list *unused
)
{
... when != _origarg
}
Signed-off-by: Kees Cook <keescook@chromium.org>
2017-10-17 00:43:17 +03:00
|
|
|
static void igmp_gq_timer_expire(struct timer_list *t)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
treewide: setup_timer() -> timer_setup()
This converts all remaining cases of the old setup_timer() API into using
timer_setup(), where the callback argument is the structure already
holding the struct timer_list. These should have no behavioral changes,
since they just change which pointer is passed into the callback with
the same available pointers after conversion. It handles the following
examples, in addition to some other variations.
Casting from unsigned long:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
setup_timer(&ptr->my_timer, my_callback, ptr);
and forced object casts:
void my_callback(struct something *ptr)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr);
become:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
Direct function assignments:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
ptr->my_timer.function = my_callback;
have a temporary cast added, along with converting the args:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback;
And finally, callbacks without a data assignment:
void my_callback(unsigned long data)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, 0);
have their argument renamed to verify they're unused during conversion:
void my_callback(struct timer_list *unused)
{
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
The conversion is done with the following Coccinelle script:
spatch --very-quiet --all-includes --include-headers \
-I ./arch/x86/include -I ./arch/x86/include/generated \
-I ./include -I ./arch/x86/include/uapi \
-I ./arch/x86/include/generated/uapi -I ./include/uapi \
-I ./include/generated/uapi --include ./include/linux/kconfig.h \
--dir . \
--cocci-file ~/src/data/timer_setup.cocci
@fix_address_of@
expression e;
@@
setup_timer(
-&(e)
+&e
, ...)
// Update any raw setup_timer() usages that have a NULL callback, but
// would otherwise match change_timer_function_usage, since the latter
// will update all function assignments done in the face of a NULL
// function initialization in setup_timer().
@change_timer_function_usage_NULL@
expression _E;
identifier _timer;
type _cast_data;
@@
(
-setup_timer(&_E->_timer, NULL, _E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E->_timer, NULL, (_cast_data)_E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, &_E);
+timer_setup(&_E._timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, (_cast_data)&_E);
+timer_setup(&_E._timer, NULL, 0);
)
@change_timer_function_usage@
expression _E;
identifier _timer;
struct timer_list _stl;
identifier _callback;
type _cast_func, _cast_data;
@@
(
-setup_timer(&_E->_timer, _callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
_E->_timer@_stl.function = _callback;
|
_E->_timer@_stl.function = &_callback;
|
_E->_timer@_stl.function = (_cast_func)_callback;
|
_E->_timer@_stl.function = (_cast_func)&_callback;
|
_E._timer@_stl.function = _callback;
|
_E._timer@_stl.function = &_callback;
|
_E._timer@_stl.function = (_cast_func)_callback;
|
_E._timer@_stl.function = (_cast_func)&_callback;
)
// callback(unsigned long arg)
@change_callback_handle_cast
depends on change_timer_function_usage@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
identifier _handle;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
(
... when != _origarg
_handletype *_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
)
}
// callback(unsigned long arg) without existing variable
@change_callback_handle_cast_no_arg
depends on change_timer_function_usage &&
!change_callback_handle_cast@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
+ _handletype *_origarg = from_timer(_origarg, t, _timer);
+
... when != _origarg
- (_handletype *)_origarg
+ _origarg
... when != _origarg
}
// Avoid already converted callbacks.
@match_callback_converted
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier t;
@@
void _callback(struct timer_list *t)
{ ... }
// callback(struct something *handle)
@change_callback_handle_arg
depends on change_timer_function_usage &&
!match_callback_converted &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
@@
void _callback(
-_handletype *_handle
+struct timer_list *t
)
{
+ _handletype *_handle = from_timer(_handle, t, _timer);
...
}
// If change_callback_handle_arg ran on an empty function, remove
// the added handler.
@unchange_callback_handle_arg
depends on change_timer_function_usage &&
change_callback_handle_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
identifier t;
@@
void _callback(struct timer_list *t)
{
- _handletype *_handle = from_timer(_handle, t, _timer);
}
// We only want to refactor the setup_timer() data argument if we've found
// the matching callback. This undoes changes in change_timer_function_usage.
@unchange_timer_function_usage
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg &&
!change_callback_handle_arg@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type change_timer_function_usage._cast_data;
@@
(
-timer_setup(&_E->_timer, _callback, 0);
+setup_timer(&_E->_timer, _callback, (_cast_data)_E);
|
-timer_setup(&_E._timer, _callback, 0);
+setup_timer(&_E._timer, _callback, (_cast_data)&_E);
)
// If we fixed a callback from a .function assignment, fix the
// assignment cast now.
@change_timer_function_assignment
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_func;
typedef TIMER_FUNC_TYPE;
@@
(
_E->_timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-&_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
)
// Sometimes timer functions are called directly. Replace matched args.
@change_timer_function_calls
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression _E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_data;
@@
_callback(
(
-(_cast_data)_E
+&_E->_timer
|
-(_cast_data)&_E
+&_E._timer
|
-_E
+&_E->_timer
)
)
// If a timer has been configured without a data argument, it can be
// converted without regard to the callback argument, since it is unused.
@match_timer_function_unused_data@
expression _E;
identifier _timer;
identifier _callback;
@@
(
-setup_timer(&_E->_timer, _callback, 0);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0L);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0UL);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0L);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0UL);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0L);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0UL);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0L);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0UL);
+timer_setup(_timer, _callback, 0);
)
@change_callback_unused_data
depends on match_timer_function_unused_data@
identifier match_timer_function_unused_data._callback;
type _origtype;
identifier _origarg;
@@
void _callback(
-_origtype _origarg
+struct timer_list *unused
)
{
... when != _origarg
}
Signed-off-by: Kees Cook <keescook@chromium.org>
2017-10-17 00:43:17 +03:00
|
|
|
struct in_device *in_dev = from_timer(in_dev, t, mr_gq_timer);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
in_dev->mr_gq_running = 0;
|
|
|
|
igmpv3_send_report(in_dev, NULL);
|
2013-09-30 00:39:42 +04:00
|
|
|
in_dev_put(in_dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
treewide: setup_timer() -> timer_setup()
This converts all remaining cases of the old setup_timer() API into using
timer_setup(), where the callback argument is the structure already
holding the struct timer_list. These should have no behavioral changes,
since they just change which pointer is passed into the callback with
the same available pointers after conversion. It handles the following
examples, in addition to some other variations.
Casting from unsigned long:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
setup_timer(&ptr->my_timer, my_callback, ptr);
and forced object casts:
void my_callback(struct something *ptr)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr);
become:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
Direct function assignments:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
ptr->my_timer.function = my_callback;
have a temporary cast added, along with converting the args:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback;
And finally, callbacks without a data assignment:
void my_callback(unsigned long data)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, 0);
have their argument renamed to verify they're unused during conversion:
void my_callback(struct timer_list *unused)
{
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
The conversion is done with the following Coccinelle script:
spatch --very-quiet --all-includes --include-headers \
-I ./arch/x86/include -I ./arch/x86/include/generated \
-I ./include -I ./arch/x86/include/uapi \
-I ./arch/x86/include/generated/uapi -I ./include/uapi \
-I ./include/generated/uapi --include ./include/linux/kconfig.h \
--dir . \
--cocci-file ~/src/data/timer_setup.cocci
@fix_address_of@
expression e;
@@
setup_timer(
-&(e)
+&e
, ...)
// Update any raw setup_timer() usages that have a NULL callback, but
// would otherwise match change_timer_function_usage, since the latter
// will update all function assignments done in the face of a NULL
// function initialization in setup_timer().
@change_timer_function_usage_NULL@
expression _E;
identifier _timer;
type _cast_data;
@@
(
-setup_timer(&_E->_timer, NULL, _E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E->_timer, NULL, (_cast_data)_E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, &_E);
+timer_setup(&_E._timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, (_cast_data)&_E);
+timer_setup(&_E._timer, NULL, 0);
)
@change_timer_function_usage@
expression _E;
identifier _timer;
struct timer_list _stl;
identifier _callback;
type _cast_func, _cast_data;
@@
(
-setup_timer(&_E->_timer, _callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
_E->_timer@_stl.function = _callback;
|
_E->_timer@_stl.function = &_callback;
|
_E->_timer@_stl.function = (_cast_func)_callback;
|
_E->_timer@_stl.function = (_cast_func)&_callback;
|
_E._timer@_stl.function = _callback;
|
_E._timer@_stl.function = &_callback;
|
_E._timer@_stl.function = (_cast_func)_callback;
|
_E._timer@_stl.function = (_cast_func)&_callback;
)
// callback(unsigned long arg)
@change_callback_handle_cast
depends on change_timer_function_usage@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
identifier _handle;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
(
... when != _origarg
_handletype *_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
)
}
// callback(unsigned long arg) without existing variable
@change_callback_handle_cast_no_arg
depends on change_timer_function_usage &&
!change_callback_handle_cast@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
+ _handletype *_origarg = from_timer(_origarg, t, _timer);
+
... when != _origarg
- (_handletype *)_origarg
+ _origarg
... when != _origarg
}
// Avoid already converted callbacks.
@match_callback_converted
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier t;
@@
void _callback(struct timer_list *t)
{ ... }
// callback(struct something *handle)
@change_callback_handle_arg
depends on change_timer_function_usage &&
!match_callback_converted &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
@@
void _callback(
-_handletype *_handle
+struct timer_list *t
)
{
+ _handletype *_handle = from_timer(_handle, t, _timer);
...
}
// If change_callback_handle_arg ran on an empty function, remove
// the added handler.
@unchange_callback_handle_arg
depends on change_timer_function_usage &&
change_callback_handle_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
identifier t;
@@
void _callback(struct timer_list *t)
{
- _handletype *_handle = from_timer(_handle, t, _timer);
}
// We only want to refactor the setup_timer() data argument if we've found
// the matching callback. This undoes changes in change_timer_function_usage.
@unchange_timer_function_usage
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg &&
!change_callback_handle_arg@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type change_timer_function_usage._cast_data;
@@
(
-timer_setup(&_E->_timer, _callback, 0);
+setup_timer(&_E->_timer, _callback, (_cast_data)_E);
|
-timer_setup(&_E._timer, _callback, 0);
+setup_timer(&_E._timer, _callback, (_cast_data)&_E);
)
// If we fixed a callback from a .function assignment, fix the
// assignment cast now.
@change_timer_function_assignment
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_func;
typedef TIMER_FUNC_TYPE;
@@
(
_E->_timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-&_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
)
// Sometimes timer functions are called directly. Replace matched args.
@change_timer_function_calls
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression _E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_data;
@@
_callback(
(
-(_cast_data)_E
+&_E->_timer
|
-(_cast_data)&_E
+&_E._timer
|
-_E
+&_E->_timer
)
)
// If a timer has been configured without a data argument, it can be
// converted without regard to the callback argument, since it is unused.
@match_timer_function_unused_data@
expression _E;
identifier _timer;
identifier _callback;
@@
(
-setup_timer(&_E->_timer, _callback, 0);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0L);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0UL);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0L);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0UL);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0L);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0UL);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0L);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0UL);
+timer_setup(_timer, _callback, 0);
)
@change_callback_unused_data
depends on match_timer_function_unused_data@
identifier match_timer_function_unused_data._callback;
type _origtype;
identifier _origarg;
@@
void _callback(
-_origtype _origarg
+struct timer_list *unused
)
{
... when != _origarg
}
Signed-off-by: Kees Cook <keescook@chromium.org>
2017-10-17 00:43:17 +03:00
|
|
|
static void igmp_ifc_timer_expire(struct timer_list *t)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
treewide: setup_timer() -> timer_setup()
This converts all remaining cases of the old setup_timer() API into using
timer_setup(), where the callback argument is the structure already
holding the struct timer_list. These should have no behavioral changes,
since they just change which pointer is passed into the callback with
the same available pointers after conversion. It handles the following
examples, in addition to some other variations.
Casting from unsigned long:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
setup_timer(&ptr->my_timer, my_callback, ptr);
and forced object casts:
void my_callback(struct something *ptr)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr);
become:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
Direct function assignments:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
ptr->my_timer.function = my_callback;
have a temporary cast added, along with converting the args:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback;
And finally, callbacks without a data assignment:
void my_callback(unsigned long data)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, 0);
have their argument renamed to verify they're unused during conversion:
void my_callback(struct timer_list *unused)
{
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
The conversion is done with the following Coccinelle script:
spatch --very-quiet --all-includes --include-headers \
-I ./arch/x86/include -I ./arch/x86/include/generated \
-I ./include -I ./arch/x86/include/uapi \
-I ./arch/x86/include/generated/uapi -I ./include/uapi \
-I ./include/generated/uapi --include ./include/linux/kconfig.h \
--dir . \
--cocci-file ~/src/data/timer_setup.cocci
@fix_address_of@
expression e;
@@
setup_timer(
-&(e)
+&e
, ...)
// Update any raw setup_timer() usages that have a NULL callback, but
// would otherwise match change_timer_function_usage, since the latter
// will update all function assignments done in the face of a NULL
// function initialization in setup_timer().
@change_timer_function_usage_NULL@
expression _E;
identifier _timer;
type _cast_data;
@@
(
-setup_timer(&_E->_timer, NULL, _E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E->_timer, NULL, (_cast_data)_E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, &_E);
+timer_setup(&_E._timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, (_cast_data)&_E);
+timer_setup(&_E._timer, NULL, 0);
)
@change_timer_function_usage@
expression _E;
identifier _timer;
struct timer_list _stl;
identifier _callback;
type _cast_func, _cast_data;
@@
(
-setup_timer(&_E->_timer, _callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
_E->_timer@_stl.function = _callback;
|
_E->_timer@_stl.function = &_callback;
|
_E->_timer@_stl.function = (_cast_func)_callback;
|
_E->_timer@_stl.function = (_cast_func)&_callback;
|
_E._timer@_stl.function = _callback;
|
_E._timer@_stl.function = &_callback;
|
_E._timer@_stl.function = (_cast_func)_callback;
|
_E._timer@_stl.function = (_cast_func)&_callback;
)
// callback(unsigned long arg)
@change_callback_handle_cast
depends on change_timer_function_usage@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
identifier _handle;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
(
... when != _origarg
_handletype *_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
)
}
// callback(unsigned long arg) without existing variable
@change_callback_handle_cast_no_arg
depends on change_timer_function_usage &&
!change_callback_handle_cast@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
+ _handletype *_origarg = from_timer(_origarg, t, _timer);
+
... when != _origarg
- (_handletype *)_origarg
+ _origarg
... when != _origarg
}
// Avoid already converted callbacks.
@match_callback_converted
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier t;
@@
void _callback(struct timer_list *t)
{ ... }
// callback(struct something *handle)
@change_callback_handle_arg
depends on change_timer_function_usage &&
!match_callback_converted &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
@@
void _callback(
-_handletype *_handle
+struct timer_list *t
)
{
+ _handletype *_handle = from_timer(_handle, t, _timer);
...
}
// If change_callback_handle_arg ran on an empty function, remove
// the added handler.
@unchange_callback_handle_arg
depends on change_timer_function_usage &&
change_callback_handle_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
identifier t;
@@
void _callback(struct timer_list *t)
{
- _handletype *_handle = from_timer(_handle, t, _timer);
}
// We only want to refactor the setup_timer() data argument if we've found
// the matching callback. This undoes changes in change_timer_function_usage.
@unchange_timer_function_usage
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg &&
!change_callback_handle_arg@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type change_timer_function_usage._cast_data;
@@
(
-timer_setup(&_E->_timer, _callback, 0);
+setup_timer(&_E->_timer, _callback, (_cast_data)_E);
|
-timer_setup(&_E._timer, _callback, 0);
+setup_timer(&_E._timer, _callback, (_cast_data)&_E);
)
// If we fixed a callback from a .function assignment, fix the
// assignment cast now.
@change_timer_function_assignment
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_func;
typedef TIMER_FUNC_TYPE;
@@
(
_E->_timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-&_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
)
// Sometimes timer functions are called directly. Replace matched args.
@change_timer_function_calls
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression _E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_data;
@@
_callback(
(
-(_cast_data)_E
+&_E->_timer
|
-(_cast_data)&_E
+&_E._timer
|
-_E
+&_E->_timer
)
)
// If a timer has been configured without a data argument, it can be
// converted without regard to the callback argument, since it is unused.
@match_timer_function_unused_data@
expression _E;
identifier _timer;
identifier _callback;
@@
(
-setup_timer(&_E->_timer, _callback, 0);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0L);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0UL);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0L);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0UL);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0L);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0UL);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0L);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0UL);
+timer_setup(_timer, _callback, 0);
)
@change_callback_unused_data
depends on match_timer_function_unused_data@
identifier match_timer_function_unused_data._callback;
type _origtype;
identifier _origarg;
@@
void _callback(
-_origtype _origarg
+struct timer_list *unused
)
{
... when != _origarg
}
Signed-off-by: Kees Cook <keescook@chromium.org>
2017-10-17 00:43:17 +03:00
|
|
|
struct in_device *in_dev = from_timer(in_dev, t, mr_ifc_timer);
|
2021-08-11 22:57:15 +03:00
|
|
|
u32 mr_ifc_count;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
igmpv3_send_cr(in_dev);
|
2021-08-10 12:45:47 +03:00
|
|
|
restart:
|
|
|
|
mr_ifc_count = READ_ONCE(in_dev->mr_ifc_count);
|
|
|
|
|
|
|
|
if (mr_ifc_count) {
|
|
|
|
if (cmpxchg(&in_dev->mr_ifc_count,
|
|
|
|
mr_ifc_count,
|
|
|
|
mr_ifc_count - 1) != mr_ifc_count)
|
|
|
|
goto restart;
|
2013-08-06 22:03:13 +04:00
|
|
|
igmp_ifc_start_timer(in_dev,
|
|
|
|
unsolicited_report_interval(in_dev));
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
2013-09-30 00:39:42 +04:00
|
|
|
in_dev_put(in_dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void igmp_ifc_event(struct in_device *in_dev)
|
|
|
|
{
|
2016-02-09 00:29:24 +03:00
|
|
|
struct net *net = dev_net(in_dev->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (IGMP_V1_SEEN(in_dev) || IGMP_V2_SEEN(in_dev))
|
|
|
|
return;
|
2022-07-15 20:17:44 +03:00
|
|
|
WRITE_ONCE(in_dev->mr_ifc_count, in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv));
|
2005-04-17 02:20:36 +04:00
|
|
|
igmp_ifc_start_timer(in_dev, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
treewide: setup_timer() -> timer_setup()
This converts all remaining cases of the old setup_timer() API into using
timer_setup(), where the callback argument is the structure already
holding the struct timer_list. These should have no behavioral changes,
since they just change which pointer is passed into the callback with
the same available pointers after conversion. It handles the following
examples, in addition to some other variations.
Casting from unsigned long:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
setup_timer(&ptr->my_timer, my_callback, ptr);
and forced object casts:
void my_callback(struct something *ptr)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr);
become:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
Direct function assignments:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
ptr->my_timer.function = my_callback;
have a temporary cast added, along with converting the args:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback;
And finally, callbacks without a data assignment:
void my_callback(unsigned long data)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, 0);
have their argument renamed to verify they're unused during conversion:
void my_callback(struct timer_list *unused)
{
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
The conversion is done with the following Coccinelle script:
spatch --very-quiet --all-includes --include-headers \
-I ./arch/x86/include -I ./arch/x86/include/generated \
-I ./include -I ./arch/x86/include/uapi \
-I ./arch/x86/include/generated/uapi -I ./include/uapi \
-I ./include/generated/uapi --include ./include/linux/kconfig.h \
--dir . \
--cocci-file ~/src/data/timer_setup.cocci
@fix_address_of@
expression e;
@@
setup_timer(
-&(e)
+&e
, ...)
// Update any raw setup_timer() usages that have a NULL callback, but
// would otherwise match change_timer_function_usage, since the latter
// will update all function assignments done in the face of a NULL
// function initialization in setup_timer().
@change_timer_function_usage_NULL@
expression _E;
identifier _timer;
type _cast_data;
@@
(
-setup_timer(&_E->_timer, NULL, _E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E->_timer, NULL, (_cast_data)_E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, &_E);
+timer_setup(&_E._timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, (_cast_data)&_E);
+timer_setup(&_E._timer, NULL, 0);
)
@change_timer_function_usage@
expression _E;
identifier _timer;
struct timer_list _stl;
identifier _callback;
type _cast_func, _cast_data;
@@
(
-setup_timer(&_E->_timer, _callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
_E->_timer@_stl.function = _callback;
|
_E->_timer@_stl.function = &_callback;
|
_E->_timer@_stl.function = (_cast_func)_callback;
|
_E->_timer@_stl.function = (_cast_func)&_callback;
|
_E._timer@_stl.function = _callback;
|
_E._timer@_stl.function = &_callback;
|
_E._timer@_stl.function = (_cast_func)_callback;
|
_E._timer@_stl.function = (_cast_func)&_callback;
)
// callback(unsigned long arg)
@change_callback_handle_cast
depends on change_timer_function_usage@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
identifier _handle;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
(
... when != _origarg
_handletype *_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
)
}
// callback(unsigned long arg) without existing variable
@change_callback_handle_cast_no_arg
depends on change_timer_function_usage &&
!change_callback_handle_cast@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
+ _handletype *_origarg = from_timer(_origarg, t, _timer);
+
... when != _origarg
- (_handletype *)_origarg
+ _origarg
... when != _origarg
}
// Avoid already converted callbacks.
@match_callback_converted
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier t;
@@
void _callback(struct timer_list *t)
{ ... }
// callback(struct something *handle)
@change_callback_handle_arg
depends on change_timer_function_usage &&
!match_callback_converted &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
@@
void _callback(
-_handletype *_handle
+struct timer_list *t
)
{
+ _handletype *_handle = from_timer(_handle, t, _timer);
...
}
// If change_callback_handle_arg ran on an empty function, remove
// the added handler.
@unchange_callback_handle_arg
depends on change_timer_function_usage &&
change_callback_handle_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
identifier t;
@@
void _callback(struct timer_list *t)
{
- _handletype *_handle = from_timer(_handle, t, _timer);
}
// We only want to refactor the setup_timer() data argument if we've found
// the matching callback. This undoes changes in change_timer_function_usage.
@unchange_timer_function_usage
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg &&
!change_callback_handle_arg@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type change_timer_function_usage._cast_data;
@@
(
-timer_setup(&_E->_timer, _callback, 0);
+setup_timer(&_E->_timer, _callback, (_cast_data)_E);
|
-timer_setup(&_E._timer, _callback, 0);
+setup_timer(&_E._timer, _callback, (_cast_data)&_E);
)
// If we fixed a callback from a .function assignment, fix the
// assignment cast now.
@change_timer_function_assignment
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_func;
typedef TIMER_FUNC_TYPE;
@@
(
_E->_timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-&_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
)
// Sometimes timer functions are called directly. Replace matched args.
@change_timer_function_calls
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression _E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_data;
@@
_callback(
(
-(_cast_data)_E
+&_E->_timer
|
-(_cast_data)&_E
+&_E._timer
|
-_E
+&_E->_timer
)
)
// If a timer has been configured without a data argument, it can be
// converted without regard to the callback argument, since it is unused.
@match_timer_function_unused_data@
expression _E;
identifier _timer;
identifier _callback;
@@
(
-setup_timer(&_E->_timer, _callback, 0);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0L);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0UL);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0L);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0UL);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0L);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0UL);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0L);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0UL);
+timer_setup(_timer, _callback, 0);
)
@change_callback_unused_data
depends on match_timer_function_unused_data@
identifier match_timer_function_unused_data._callback;
type _origtype;
identifier _origarg;
@@
void _callback(
-_origtype _origarg
+struct timer_list *unused
)
{
... when != _origarg
}
Signed-off-by: Kees Cook <keescook@chromium.org>
2017-10-17 00:43:17 +03:00
|
|
|
static void igmp_timer_expire(struct timer_list *t)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
treewide: setup_timer() -> timer_setup()
This converts all remaining cases of the old setup_timer() API into using
timer_setup(), where the callback argument is the structure already
holding the struct timer_list. These should have no behavioral changes,
since they just change which pointer is passed into the callback with
the same available pointers after conversion. It handles the following
examples, in addition to some other variations.
Casting from unsigned long:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
setup_timer(&ptr->my_timer, my_callback, ptr);
and forced object casts:
void my_callback(struct something *ptr)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr);
become:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
Direct function assignments:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
ptr->my_timer.function = my_callback;
have a temporary cast added, along with converting the args:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback;
And finally, callbacks without a data assignment:
void my_callback(unsigned long data)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, 0);
have their argument renamed to verify they're unused during conversion:
void my_callback(struct timer_list *unused)
{
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
The conversion is done with the following Coccinelle script:
spatch --very-quiet --all-includes --include-headers \
-I ./arch/x86/include -I ./arch/x86/include/generated \
-I ./include -I ./arch/x86/include/uapi \
-I ./arch/x86/include/generated/uapi -I ./include/uapi \
-I ./include/generated/uapi --include ./include/linux/kconfig.h \
--dir . \
--cocci-file ~/src/data/timer_setup.cocci
@fix_address_of@
expression e;
@@
setup_timer(
-&(e)
+&e
, ...)
// Update any raw setup_timer() usages that have a NULL callback, but
// would otherwise match change_timer_function_usage, since the latter
// will update all function assignments done in the face of a NULL
// function initialization in setup_timer().
@change_timer_function_usage_NULL@
expression _E;
identifier _timer;
type _cast_data;
@@
(
-setup_timer(&_E->_timer, NULL, _E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E->_timer, NULL, (_cast_data)_E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, &_E);
+timer_setup(&_E._timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, (_cast_data)&_E);
+timer_setup(&_E._timer, NULL, 0);
)
@change_timer_function_usage@
expression _E;
identifier _timer;
struct timer_list _stl;
identifier _callback;
type _cast_func, _cast_data;
@@
(
-setup_timer(&_E->_timer, _callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
_E->_timer@_stl.function = _callback;
|
_E->_timer@_stl.function = &_callback;
|
_E->_timer@_stl.function = (_cast_func)_callback;
|
_E->_timer@_stl.function = (_cast_func)&_callback;
|
_E._timer@_stl.function = _callback;
|
_E._timer@_stl.function = &_callback;
|
_E._timer@_stl.function = (_cast_func)_callback;
|
_E._timer@_stl.function = (_cast_func)&_callback;
)
// callback(unsigned long arg)
@change_callback_handle_cast
depends on change_timer_function_usage@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
identifier _handle;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
(
... when != _origarg
_handletype *_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
)
}
// callback(unsigned long arg) without existing variable
@change_callback_handle_cast_no_arg
depends on change_timer_function_usage &&
!change_callback_handle_cast@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
+ _handletype *_origarg = from_timer(_origarg, t, _timer);
+
... when != _origarg
- (_handletype *)_origarg
+ _origarg
... when != _origarg
}
// Avoid already converted callbacks.
@match_callback_converted
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier t;
@@
void _callback(struct timer_list *t)
{ ... }
// callback(struct something *handle)
@change_callback_handle_arg
depends on change_timer_function_usage &&
!match_callback_converted &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
@@
void _callback(
-_handletype *_handle
+struct timer_list *t
)
{
+ _handletype *_handle = from_timer(_handle, t, _timer);
...
}
// If change_callback_handle_arg ran on an empty function, remove
// the added handler.
@unchange_callback_handle_arg
depends on change_timer_function_usage &&
change_callback_handle_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
identifier t;
@@
void _callback(struct timer_list *t)
{
- _handletype *_handle = from_timer(_handle, t, _timer);
}
// We only want to refactor the setup_timer() data argument if we've found
// the matching callback. This undoes changes in change_timer_function_usage.
@unchange_timer_function_usage
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg &&
!change_callback_handle_arg@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type change_timer_function_usage._cast_data;
@@
(
-timer_setup(&_E->_timer, _callback, 0);
+setup_timer(&_E->_timer, _callback, (_cast_data)_E);
|
-timer_setup(&_E._timer, _callback, 0);
+setup_timer(&_E._timer, _callback, (_cast_data)&_E);
)
// If we fixed a callback from a .function assignment, fix the
// assignment cast now.
@change_timer_function_assignment
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_func;
typedef TIMER_FUNC_TYPE;
@@
(
_E->_timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-&_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
)
// Sometimes timer functions are called directly. Replace matched args.
@change_timer_function_calls
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression _E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_data;
@@
_callback(
(
-(_cast_data)_E
+&_E->_timer
|
-(_cast_data)&_E
+&_E._timer
|
-_E
+&_E->_timer
)
)
// If a timer has been configured without a data argument, it can be
// converted without regard to the callback argument, since it is unused.
@match_timer_function_unused_data@
expression _E;
identifier _timer;
identifier _callback;
@@
(
-setup_timer(&_E->_timer, _callback, 0);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0L);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0UL);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0L);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0UL);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0L);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0UL);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0L);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0UL);
+timer_setup(_timer, _callback, 0);
)
@change_callback_unused_data
depends on match_timer_function_unused_data@
identifier match_timer_function_unused_data._callback;
type _origtype;
identifier _origarg;
@@
void _callback(
-_origtype _origarg
+struct timer_list *unused
)
{
... when != _origarg
}
Signed-off-by: Kees Cook <keescook@chromium.org>
2017-10-17 00:43:17 +03:00
|
|
|
struct ip_mc_list *im = from_timer(im, t, timer);
|
2005-04-17 02:20:36 +04:00
|
|
|
struct in_device *in_dev = im->interface;
|
|
|
|
|
|
|
|
spin_lock(&im->lock);
|
2008-11-03 11:26:09 +03:00
|
|
|
im->tm_running = 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2018-08-29 13:06:08 +03:00
|
|
|
if (im->unsolicit_count && --im->unsolicit_count)
|
2013-08-06 22:03:13 +04:00
|
|
|
igmp_start_timer(im, unsolicited_report_interval(in_dev));
|
2018-08-29 13:06:08 +03:00
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
im->reporter = 1;
|
|
|
|
spin_unlock(&im->lock);
|
|
|
|
|
|
|
|
if (IGMP_V1_SEEN(in_dev))
|
|
|
|
igmp_send_report(in_dev, im, IGMP_HOST_MEMBERSHIP_REPORT);
|
|
|
|
else if (IGMP_V2_SEEN(in_dev))
|
|
|
|
igmp_send_report(in_dev, im, IGMPV2_HOST_MEMBERSHIP_REPORT);
|
|
|
|
else
|
|
|
|
igmp_send_report(in_dev, im, IGMPV3_HOST_MEMBERSHIP_REPORT);
|
|
|
|
|
|
|
|
ip_ma_put(im);
|
|
|
|
}
|
|
|
|
|
2006-01-19 01:20:56 +03:00
|
|
|
/* mark EXCLUDE-mode sources */
|
2006-09-28 05:30:52 +04:00
|
|
|
static int igmp_xmarksources(struct ip_mc_list *pmc, int nsrcs, __be32 *srcs)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct ip_sf_list *psf;
|
|
|
|
int i, scount;
|
|
|
|
|
2006-01-19 01:20:56 +03:00
|
|
|
scount = 0;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = pmc->sources; psf; psf = psf->sf_next) {
|
2006-01-19 01:20:56 +03:00
|
|
|
if (scount == nsrcs)
|
|
|
|
break;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (i = 0; i < nsrcs; i++) {
|
2006-01-19 01:20:56 +03:00
|
|
|
/* skip inactive filters */
|
2011-08-24 02:54:37 +04:00
|
|
|
if (psf->sf_count[MCAST_INCLUDE] ||
|
2006-01-19 01:20:56 +03:00
|
|
|
pmc->sfcount[MCAST_EXCLUDE] !=
|
|
|
|
psf->sf_count[MCAST_EXCLUDE])
|
2012-04-05 13:36:29 +04:00
|
|
|
break;
|
2006-01-19 01:20:56 +03:00
|
|
|
if (srcs[i] == psf->sf_inaddr) {
|
|
|
|
scount++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pmc->gsquery = 0;
|
|
|
|
if (scount == nsrcs) /* all sources excluded */
|
|
|
|
return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2006-09-28 05:30:52 +04:00
|
|
|
static int igmp_marksources(struct ip_mc_list *pmc, int nsrcs, __be32 *srcs)
|
2006-01-19 01:20:56 +03:00
|
|
|
{
|
|
|
|
struct ip_sf_list *psf;
|
|
|
|
int i, scount;
|
|
|
|
|
|
|
|
if (pmc->sfmode == MCAST_EXCLUDE)
|
|
|
|
return igmp_xmarksources(pmc, nsrcs, srcs);
|
|
|
|
|
|
|
|
/* mark INCLUDE-mode sources */
|
2005-04-17 02:20:36 +04:00
|
|
|
scount = 0;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = pmc->sources; psf; psf = psf->sf_next) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (scount == nsrcs)
|
|
|
|
break;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (i = 0; i < nsrcs; i++)
|
2005-04-17 02:20:36 +04:00
|
|
|
if (srcs[i] == psf->sf_inaddr) {
|
|
|
|
psf->sf_gsresp = 1;
|
|
|
|
scount++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2006-01-19 01:20:56 +03:00
|
|
|
if (!scount) {
|
|
|
|
pmc->gsquery = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
pmc->gsquery = 1;
|
|
|
|
return 1;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
2012-09-07 00:37:06 +04:00
|
|
|
/* return true if packet was dropped */
|
|
|
|
static bool igmp_heard_report(struct in_device *in_dev, __be32 group)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct ip_mc_list *im;
|
2016-02-09 01:13:50 +03:00
|
|
|
struct net *net = dev_net(in_dev->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
/* Timers are only set for non-local groups */
|
|
|
|
|
|
|
|
if (group == IGMP_ALL_HOSTS)
|
2012-09-07 00:37:06 +04:00
|
|
|
return false;
|
2022-07-15 20:17:41 +03:00
|
|
|
if (ipv4_is_local_multicast(group) &&
|
|
|
|
!READ_ONCE(net->ipv4.sysctl_igmp_llm_reports))
|
2015-08-27 18:46:26 +03:00
|
|
|
return false;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_lock();
|
|
|
|
for_each_pmc_rcu(in_dev, im) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (im->multiaddr == group) {
|
|
|
|
igmp_stop_timer(im);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_unlock();
|
2012-09-07 00:37:06 +04:00
|
|
|
return false;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
2012-09-07 00:37:06 +04:00
|
|
|
/* return true if packet was dropped */
|
|
|
|
static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
|
2005-04-17 02:20:36 +04:00
|
|
|
int len)
|
|
|
|
{
|
2007-03-13 20:19:23 +03:00
|
|
|
struct igmphdr *ih = igmp_hdr(skb);
|
|
|
|
struct igmpv3_query *ih3 = igmpv3_query_hdr(skb);
|
2005-04-17 02:20:36 +04:00
|
|
|
struct ip_mc_list *im;
|
2006-09-28 05:31:32 +04:00
|
|
|
__be32 group = ih->group;
|
2005-04-17 02:20:36 +04:00
|
|
|
int max_delay;
|
|
|
|
int mark = 0;
|
2016-02-09 01:13:50 +03:00
|
|
|
struct net *net = dev_net(in_dev->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
|
2010-09-30 18:29:40 +04:00
|
|
|
if (len == 8) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (ih->code == 0) {
|
|
|
|
/* Alas, old v1 router presents here. */
|
2007-02-09 17:24:47 +03:00
|
|
|
|
2014-11-04 22:52:14 +03:00
|
|
|
max_delay = IGMP_QUERY_RESPONSE_INTERVAL;
|
2005-04-17 02:20:36 +04:00
|
|
|
in_dev->mr_v1_seen = jiffies +
|
2018-10-26 06:30:35 +03:00
|
|
|
(in_dev->mr_qrv * in_dev->mr_qi) +
|
|
|
|
in_dev->mr_qri;
|
2005-04-17 02:20:36 +04:00
|
|
|
group = 0;
|
|
|
|
} else {
|
|
|
|
/* v2 router present */
|
|
|
|
max_delay = ih->code*(HZ/IGMP_TIMER_SCALE);
|
|
|
|
in_dev->mr_v2_seen = jiffies +
|
2018-10-26 06:30:35 +03:00
|
|
|
(in_dev->mr_qrv * in_dev->mr_qi) +
|
|
|
|
in_dev->mr_qri;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
/* cancel the interface change timer */
|
2021-08-10 12:45:47 +03:00
|
|
|
WRITE_ONCE(in_dev->mr_ifc_count, 0);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (del_timer(&in_dev->mr_ifc_timer))
|
|
|
|
__in_dev_put(in_dev);
|
|
|
|
/* clear deleted report items */
|
|
|
|
igmpv3_clear_delrec(in_dev);
|
|
|
|
} else if (len < 12) {
|
2012-09-07 00:37:06 +04:00
|
|
|
return true; /* ignore bogus packet; freed by caller */
|
2010-09-30 18:29:40 +04:00
|
|
|
} else if (IGMP_V1_SEEN(in_dev)) {
|
|
|
|
/* This is a v3 query with v1 queriers present */
|
2014-11-04 22:52:14 +03:00
|
|
|
max_delay = IGMP_QUERY_RESPONSE_INTERVAL;
|
2010-09-30 18:29:40 +04:00
|
|
|
group = 0;
|
|
|
|
} else if (IGMP_V2_SEEN(in_dev)) {
|
|
|
|
/* this is a v3 query with v2 queriers present;
|
|
|
|
* Interpretation of the max_delay code is problematic here.
|
|
|
|
* A real v2 host would use ih_code directly, while v3 has a
|
|
|
|
* different encoding. We use the v3 encoding as more likely
|
|
|
|
* to be intended in a v3 query.
|
|
|
|
*/
|
|
|
|
max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
|
2012-01-10 02:06:46 +04:00
|
|
|
if (!max_delay)
|
|
|
|
max_delay = 1; /* can't mod w/ 0 */
|
2005-04-17 02:20:36 +04:00
|
|
|
} else { /* v3 */
|
|
|
|
if (!pskb_may_pull(skb, sizeof(struct igmpv3_query)))
|
2012-09-07 00:37:06 +04:00
|
|
|
return true;
|
2007-02-09 17:24:47 +03:00
|
|
|
|
2007-03-13 20:19:23 +03:00
|
|
|
ih3 = igmpv3_query_hdr(skb);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (ih3->nsrcs) {
|
2007-02-09 17:24:47 +03:00
|
|
|
if (!pskb_may_pull(skb, sizeof(struct igmpv3_query)
|
2006-09-28 05:31:32 +04:00
|
|
|
+ ntohs(ih3->nsrcs)*sizeof(__be32)))
|
2012-09-07 00:37:06 +04:00
|
|
|
return true;
|
2007-03-13 20:19:23 +03:00
|
|
|
ih3 = igmpv3_query_hdr(skb);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
|
|
|
|
if (!max_delay)
|
|
|
|
max_delay = 1; /* can't mod w/ 0 */
|
|
|
|
in_dev->mr_maxdelay = max_delay;
|
2018-10-26 06:30:35 +03:00
|
|
|
|
|
|
|
/* RFC3376, 4.1.6. QRV and 4.1.7. QQIC, when the most recently
|
|
|
|
* received value was zero, use the default or statically
|
|
|
|
* configured value.
|
|
|
|
*/
|
2022-07-15 20:17:44 +03:00
|
|
|
in_dev->mr_qrv = ih3->qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv);
|
2018-10-26 06:30:35 +03:00
|
|
|
in_dev->mr_qi = IGMPV3_QQIC(ih3->qqic)*HZ ?: IGMP_QUERY_INTERVAL;
|
|
|
|
|
|
|
|
/* RFC3376, 8.3. Query Response Interval:
|
|
|
|
* The number of seconds represented by the [Query Response
|
|
|
|
* Interval] must be less than the [Query Interval].
|
|
|
|
*/
|
|
|
|
if (in_dev->mr_qri >= in_dev->mr_qi)
|
|
|
|
in_dev->mr_qri = (in_dev->mr_qi/HZ - 1)*HZ;
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!group) { /* general query */
|
|
|
|
if (ih3->nsrcs)
|
2014-10-05 19:27:50 +04:00
|
|
|
return true; /* no sources allowed */
|
2005-04-17 02:20:36 +04:00
|
|
|
igmp_gq_start_timer(in_dev);
|
2012-09-07 00:37:06 +04:00
|
|
|
return false;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
/* mark sources to include, if group & source-specific */
|
|
|
|
mark = ih3->nsrcs != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* - Start the timers in all of our membership records
|
|
|
|
* that the query applies to for the interface on
|
|
|
|
* which the query arrived excl. those that belong
|
|
|
|
* to a "local" group (224.0.0.X)
|
|
|
|
* - For timers already running check if they need to
|
|
|
|
* be reset.
|
|
|
|
* - Use the igmp->igmp_code field as the maximum
|
|
|
|
* delay possible
|
|
|
|
*/
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_lock();
|
|
|
|
for_each_pmc_rcu(in_dev, im) {
|
2006-01-19 01:20:56 +03:00
|
|
|
int changed;
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
if (group && group != im->multiaddr)
|
|
|
|
continue;
|
|
|
|
if (im->multiaddr == IGMP_ALL_HOSTS)
|
|
|
|
continue;
|
2015-08-27 18:46:26 +03:00
|
|
|
if (ipv4_is_local_multicast(im->multiaddr) &&
|
2022-07-15 20:17:41 +03:00
|
|
|
!READ_ONCE(net->ipv4.sysctl_igmp_llm_reports))
|
2015-08-27 18:46:26 +03:00
|
|
|
continue;
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock_bh(&im->lock);
|
|
|
|
if (im->tm_running)
|
|
|
|
im->gsquery = im->gsquery && mark;
|
|
|
|
else
|
|
|
|
im->gsquery = mark;
|
2006-01-19 01:20:56 +03:00
|
|
|
changed = !im->gsquery ||
|
2007-02-09 17:24:47 +03:00
|
|
|
igmp_marksources(im, ntohs(ih3->nsrcs), ih3->srcs);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_unlock_bh(&im->lock);
|
2006-01-19 01:20:56 +03:00
|
|
|
if (changed)
|
|
|
|
igmp_mod_timer(im, max_delay);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_unlock();
|
2012-09-07 00:37:06 +04:00
|
|
|
return false;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
2010-06-07 07:17:10 +04:00
|
|
|
/* called in rcu_read_lock() section */
|
2005-04-17 02:20:36 +04:00
|
|
|
int igmp_rcv(struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
/* This basically follows the spec line by line -- see RFC1112 */
|
|
|
|
struct igmphdr *ih;
|
2017-08-16 04:38:42 +03:00
|
|
|
struct net_device *dev = skb->dev;
|
|
|
|
struct in_device *in_dev;
|
2005-04-17 02:20:36 +04:00
|
|
|
int len = skb->len;
|
2012-09-07 00:37:06 +04:00
|
|
|
bool dropped = true;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2017-08-16 04:38:42 +03:00
|
|
|
if (netif_is_l3_master(dev)) {
|
|
|
|
dev = dev_get_by_index_rcu(dev_net(dev), IPCB(skb)->iif);
|
|
|
|
if (!dev)
|
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
|
|
|
in_dev = __in_dev_get_rcu(dev);
|
2015-04-03 11:17:26 +03:00
|
|
|
if (!in_dev)
|
2008-02-10 10:22:26 +03:00
|
|
|
goto drop;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-11-11 00:01:24 +03:00
|
|
|
if (!pskb_may_pull(skb, sizeof(struct igmphdr)))
|
2010-06-07 07:17:10 +04:00
|
|
|
goto drop;
|
2005-11-11 00:01:24 +03:00
|
|
|
|
2014-05-08 03:52:10 +04:00
|
|
|
if (skb_checksum_simple_validate(skb))
|
|
|
|
goto drop;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2007-03-13 20:19:23 +03:00
|
|
|
ih = igmp_hdr(skb);
|
2005-04-17 02:20:36 +04:00
|
|
|
switch (ih->type) {
|
|
|
|
case IGMP_HOST_MEMBERSHIP_QUERY:
|
2012-09-07 00:37:06 +04:00
|
|
|
dropped = igmp_heard_query(in_dev, skb, len);
|
2005-04-17 02:20:36 +04:00
|
|
|
break;
|
|
|
|
case IGMP_HOST_MEMBERSHIP_REPORT:
|
|
|
|
case IGMPV2_HOST_MEMBERSHIP_REPORT:
|
|
|
|
/* Is it our report looped back? */
|
2010-11-12 04:07:48 +03:00
|
|
|
if (rt_is_output_route(skb_rtable(skb)))
|
2005-04-17 02:20:36 +04:00
|
|
|
break;
|
2005-12-03 07:32:59 +03:00
|
|
|
/* don't rely on MC router hearing unicast reports */
|
|
|
|
if (skb->pkt_type == PACKET_MULTICAST ||
|
|
|
|
skb->pkt_type == PACKET_BROADCAST)
|
2012-09-07 00:37:06 +04:00
|
|
|
dropped = igmp_heard_report(in_dev, ih->group);
|
2005-04-17 02:20:36 +04:00
|
|
|
break;
|
|
|
|
case IGMP_PIM:
|
|
|
|
#ifdef CONFIG_IP_PIMSM_V1
|
|
|
|
return pim_rcv_v1(skb);
|
|
|
|
#endif
|
2010-02-07 20:26:30 +03:00
|
|
|
case IGMPV3_HOST_MEMBERSHIP_REPORT:
|
2005-04-17 02:20:36 +04:00
|
|
|
case IGMP_DVMRP:
|
|
|
|
case IGMP_TRACE:
|
|
|
|
case IGMP_HOST_LEAVE_MESSAGE:
|
|
|
|
case IGMP_MTRACE:
|
|
|
|
case IGMP_MTRACE_RESP:
|
|
|
|
break;
|
|
|
|
default:
|
2006-02-01 00:11:41 +03:00
|
|
|
break;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
2005-11-11 00:01:24 +03:00
|
|
|
|
2008-02-10 10:22:26 +03:00
|
|
|
drop:
|
2012-09-07 00:37:06 +04:00
|
|
|
if (dropped)
|
|
|
|
kfree_skb(skb);
|
|
|
|
else
|
|
|
|
consume_skb(skb);
|
2005-04-17 02:20:36 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add a filter to a device
|
|
|
|
*/
|
|
|
|
|
2006-09-28 05:31:32 +04:00
|
|
|
static void ip_mc_filter_add(struct in_device *in_dev, __be32 addr)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
char buf[MAX_ADDR_LEN];
|
|
|
|
struct net_device *dev = in_dev->dev;
|
|
|
|
|
|
|
|
/* Checking for IFF_MULTICAST here is WRONG-WRONG-WRONG.
|
|
|
|
We will get multicast token leakage, when IFF_MULTICAST
|
2011-08-16 10:29:02 +04:00
|
|
|
is changed. This check should be done in ndo_set_rx_mode
|
2005-04-17 02:20:36 +04:00
|
|
|
routine. Something sort of:
|
|
|
|
if (dev->mc_list && dev->flags&IFF_MULTICAST) { do it; }
|
|
|
|
--ANK
|
|
|
|
*/
|
|
|
|
if (arp_mc_map(addr, buf, dev, 0) == 0)
|
2010-04-02 01:22:57 +04:00
|
|
|
dev_mc_add(dev, buf);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Remove a filter from a device
|
|
|
|
*/
|
|
|
|
|
2006-09-28 05:31:32 +04:00
|
|
|
static void ip_mc_filter_del(struct in_device *in_dev, __be32 addr)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
char buf[MAX_ADDR_LEN];
|
|
|
|
struct net_device *dev = in_dev->dev;
|
|
|
|
|
|
|
|
if (arp_mc_map(addr, buf, dev, 0) == 0)
|
2010-04-02 01:22:57 +04:00
|
|
|
dev_mc_del(dev, buf);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
/*
|
|
|
|
* deleted ip_mc_list manipulation
|
|
|
|
*/
|
2019-02-02 07:20:52 +03:00
|
|
|
static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im,
|
|
|
|
gfp_t gfp)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct ip_mc_list *pmc;
|
2016-02-09 00:29:24 +03:00
|
|
|
struct net *net = dev_net(in_dev->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
/* this is an "ip_mc_list" for convenience; only the fields below
|
|
|
|
* are actually used. In particular, the refcnt and users are not
|
|
|
|
* used for management of the delete list. Using the same structure
|
|
|
|
* for deleted items allows change reports to use common code with
|
|
|
|
* non-deleted or query-response MCA's.
|
|
|
|
*/
|
2019-02-02 07:20:52 +03:00
|
|
|
pmc = kzalloc(sizeof(*pmc), gfp);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!pmc)
|
|
|
|
return;
|
2017-06-20 20:46:27 +03:00
|
|
|
spin_lock_init(&pmc->lock);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock_bh(&im->lock);
|
|
|
|
pmc->interface = im->interface;
|
|
|
|
in_dev_hold(in_dev);
|
|
|
|
pmc->multiaddr = im->multiaddr;
|
2022-07-15 20:17:44 +03:00
|
|
|
pmc->crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv);
|
2005-04-17 02:20:36 +04:00
|
|
|
pmc->sfmode = im->sfmode;
|
|
|
|
if (pmc->sfmode == MCAST_INCLUDE) {
|
|
|
|
struct ip_sf_list *psf;
|
|
|
|
|
|
|
|
pmc->tomb = im->tomb;
|
|
|
|
pmc->sources = im->sources;
|
|
|
|
im->tomb = im->sources = NULL;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = pmc->sources; psf; psf = psf->sf_next)
|
2005-04-17 02:20:36 +04:00
|
|
|
psf->sf_crcount = pmc->crcount;
|
|
|
|
}
|
|
|
|
spin_unlock_bh(&im->lock);
|
|
|
|
|
|
|
|
spin_lock_bh(&in_dev->mc_tomb_lock);
|
|
|
|
pmc->next = in_dev->mc_tomb;
|
|
|
|
in_dev->mc_tomb = pmc;
|
|
|
|
spin_unlock_bh(&in_dev->mc_tomb_lock);
|
|
|
|
}
|
|
|
|
|
2016-11-14 11:16:28 +03:00
|
|
|
/*
|
|
|
|
* restore ip_mc_list deleted records
|
|
|
|
*/
|
|
|
|
static void igmpv3_del_delrec(struct in_device *in_dev, struct ip_mc_list *im)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct ip_mc_list *pmc, *pmc_prev;
|
2016-11-14 11:16:28 +03:00
|
|
|
struct ip_sf_list *psf;
|
|
|
|
struct net *net = dev_net(in_dev->dev);
|
|
|
|
__be32 multiaddr = im->multiaddr;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
spin_lock_bh(&in_dev->mc_tomb_lock);
|
|
|
|
pmc_prev = NULL;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (pmc = in_dev->mc_tomb; pmc; pmc = pmc->next) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc->multiaddr == multiaddr)
|
|
|
|
break;
|
|
|
|
pmc_prev = pmc;
|
|
|
|
}
|
|
|
|
if (pmc) {
|
|
|
|
if (pmc_prev)
|
|
|
|
pmc_prev->next = pmc->next;
|
|
|
|
else
|
|
|
|
in_dev->mc_tomb = pmc->next;
|
|
|
|
}
|
|
|
|
spin_unlock_bh(&in_dev->mc_tomb_lock);
|
2016-11-14 11:16:28 +03:00
|
|
|
|
|
|
|
spin_lock_bh(&im->lock);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc) {
|
2016-11-14 11:16:28 +03:00
|
|
|
im->interface = pmc->interface;
|
2018-07-20 09:04:27 +03:00
|
|
|
if (im->sfmode == MCAST_INCLUDE) {
|
igmp: fix memory leak in igmpv3_del_delrec()
im->tomb and/or im->sources might not be NULL, but we
currently overwrite their values blindly.
Using swap() will make sure the following call to kfree_pmc(pmc)
will properly free the psf structures.
Tested with the C repro provided by syzbot, which basically does :
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
setsockopt(3, SOL_IP, IP_ADD_MEMBERSHIP, "\340\0\0\2\177\0\0\1\0\0\0\0", 12) = 0
ioctl(3, SIOCSIFFLAGS, {ifr_name="lo", ifr_flags=0}) = 0
setsockopt(3, SOL_IP, IP_MSFILTER, "\340\0\0\2\177\0\0\1\1\0\0\0\1\0\0\0\377\377\377\377", 20) = 0
ioctl(3, SIOCSIFFLAGS, {ifr_name="lo", ifr_flags=IFF_UP}) = 0
exit_group(0) = ?
BUG: memory leak
unreferenced object 0xffff88811450f140 (size 64):
comm "softirq", pid 0, jiffies 4294942448 (age 32.070s)
hex dump (first 32 bytes):
00 00 00 00 00 00 00 00 ff ff ff ff 00 00 00 00 ................
00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
backtrace:
[<00000000c7bad083>] kmemleak_alloc_recursive include/linux/kmemleak.h:43 [inline]
[<00000000c7bad083>] slab_post_alloc_hook mm/slab.h:439 [inline]
[<00000000c7bad083>] slab_alloc mm/slab.c:3326 [inline]
[<00000000c7bad083>] kmem_cache_alloc_trace+0x13d/0x280 mm/slab.c:3553
[<000000009acc4151>] kmalloc include/linux/slab.h:547 [inline]
[<000000009acc4151>] kzalloc include/linux/slab.h:742 [inline]
[<000000009acc4151>] ip_mc_add1_src net/ipv4/igmp.c:1976 [inline]
[<000000009acc4151>] ip_mc_add_src+0x36b/0x400 net/ipv4/igmp.c:2100
[<000000004ac14566>] ip_mc_msfilter+0x22d/0x310 net/ipv4/igmp.c:2484
[<0000000052d8f995>] do_ip_setsockopt.isra.0+0x1795/0x1930 net/ipv4/ip_sockglue.c:959
[<000000004ee1e21f>] ip_setsockopt+0x3b/0xb0 net/ipv4/ip_sockglue.c:1248
[<0000000066cdfe74>] udp_setsockopt+0x4e/0x90 net/ipv4/udp.c:2618
[<000000009383a786>] sock_common_setsockopt+0x38/0x50 net/core/sock.c:3126
[<00000000d8ac0c94>] __sys_setsockopt+0x98/0x120 net/socket.c:2072
[<000000001b1e9666>] __do_sys_setsockopt net/socket.c:2083 [inline]
[<000000001b1e9666>] __se_sys_setsockopt net/socket.c:2080 [inline]
[<000000001b1e9666>] __x64_sys_setsockopt+0x26/0x30 net/socket.c:2080
[<00000000420d395e>] do_syscall_64+0x76/0x1a0 arch/x86/entry/common.c:301
[<000000007fd83a4b>] entry_SYSCALL_64_after_hwframe+0x44/0xa9
Fixes: 24803f38a5c0 ("igmp: do not remove igmp souce list info when set link down")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Hangbin Liu <liuhangbin@gmail.com>
Reported-by: syzbot+6ca1abd0db68b5173a4f@syzkaller.appspotmail.com
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-06-27 11:27:01 +03:00
|
|
|
swap(im->tomb, pmc->tomb);
|
|
|
|
swap(im->sources, pmc->sources);
|
2016-11-14 11:16:28 +03:00
|
|
|
for (psf = im->sources; psf; psf = psf->sf_next)
|
2022-07-15 20:17:44 +03:00
|
|
|
psf->sf_crcount = in_dev->mr_qrv ?:
|
|
|
|
READ_ONCE(net->ipv4.sysctl_igmp_qrv);
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
} else {
|
2022-07-15 20:17:44 +03:00
|
|
|
im->crcount = in_dev->mr_qrv ?:
|
|
|
|
READ_ONCE(net->ipv4.sysctl_igmp_qrv);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
in_dev_put(pmc->interface);
|
2019-05-23 02:51:22 +03:00
|
|
|
kfree_pmc(pmc);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
2016-11-14 11:16:28 +03:00
|
|
|
spin_unlock_bh(&im->lock);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
2016-11-14 11:16:28 +03:00
|
|
|
/*
|
|
|
|
* flush ip_mc_list deleted records
|
|
|
|
*/
|
2005-04-17 02:20:36 +04:00
|
|
|
static void igmpv3_clear_delrec(struct in_device *in_dev)
|
|
|
|
{
|
|
|
|
struct ip_mc_list *pmc, *nextpmc;
|
|
|
|
|
|
|
|
spin_lock_bh(&in_dev->mc_tomb_lock);
|
|
|
|
pmc = in_dev->mc_tomb;
|
|
|
|
in_dev->mc_tomb = NULL;
|
|
|
|
spin_unlock_bh(&in_dev->mc_tomb_lock);
|
|
|
|
|
|
|
|
for (; pmc; pmc = nextpmc) {
|
|
|
|
nextpmc = pmc->next;
|
|
|
|
ip_mc_clear_src(pmc);
|
|
|
|
in_dev_put(pmc->interface);
|
2019-05-23 02:51:22 +03:00
|
|
|
kfree_pmc(pmc);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
/* clear dead sources, too */
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_lock();
|
|
|
|
for_each_pmc_rcu(in_dev, pmc) {
|
2019-05-23 02:51:22 +03:00
|
|
|
struct ip_sf_list *psf;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
spin_lock_bh(&pmc->lock);
|
|
|
|
psf = pmc->tomb;
|
|
|
|
pmc->tomb = NULL;
|
|
|
|
spin_unlock_bh(&pmc->lock);
|
2019-05-23 02:51:22 +03:00
|
|
|
ip_sf_list_clear_all(psf);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_unlock();
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-02-02 07:20:52 +03:00
|
|
|
static void __igmp_group_dropped(struct ip_mc_list *im, gfp_t gfp)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct in_device *in_dev = im->interface;
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
2016-02-09 01:13:50 +03:00
|
|
|
struct net *net = dev_net(in_dev->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
int reporter;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (im->loaded) {
|
|
|
|
im->loaded = 0;
|
|
|
|
ip_mc_filter_del(in_dev, im->multiaddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
if (im->multiaddr == IGMP_ALL_HOSTS)
|
|
|
|
return;
|
2022-07-15 20:17:41 +03:00
|
|
|
if (ipv4_is_local_multicast(im->multiaddr) &&
|
|
|
|
!READ_ONCE(net->ipv4.sysctl_igmp_llm_reports))
|
2015-08-27 18:46:26 +03:00
|
|
|
return;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
reporter = im->reporter;
|
|
|
|
igmp_stop_timer(im);
|
|
|
|
|
|
|
|
if (!in_dev->dead) {
|
|
|
|
if (IGMP_V1_SEEN(in_dev))
|
2011-05-24 03:15:05 +04:00
|
|
|
return;
|
2005-04-17 02:20:36 +04:00
|
|
|
if (IGMP_V2_SEEN(in_dev)) {
|
|
|
|
if (reporter)
|
|
|
|
igmp_send_report(in_dev, im, IGMP_HOST_LEAVE_MESSAGE);
|
2011-05-24 03:15:05 +04:00
|
|
|
return;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
/* IGMPv3 */
|
2019-02-02 07:20:52 +03:00
|
|
|
igmpv3_add_delrec(in_dev, im, gfp);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
igmp_ifc_event(in_dev);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-02-02 07:20:52 +03:00
|
|
|
static void igmp_group_dropped(struct ip_mc_list *im)
|
|
|
|
{
|
|
|
|
__igmp_group_dropped(im, GFP_KERNEL);
|
|
|
|
}
|
|
|
|
|
2018-07-20 09:07:42 +03:00
|
|
|
static void igmp_group_added(struct ip_mc_list *im)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct in_device *in_dev = im->interface;
|
2016-02-15 13:11:28 +03:00
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
2016-02-09 01:13:50 +03:00
|
|
|
struct net *net = dev_net(in_dev->dev);
|
2016-02-15 13:11:28 +03:00
|
|
|
#endif
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (im->loaded == 0) {
|
|
|
|
im->loaded = 1;
|
|
|
|
ip_mc_filter_add(in_dev, im->multiaddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
if (im->multiaddr == IGMP_ALL_HOSTS)
|
|
|
|
return;
|
2022-07-15 20:17:41 +03:00
|
|
|
if (ipv4_is_local_multicast(im->multiaddr) &&
|
|
|
|
!READ_ONCE(net->ipv4.sysctl_igmp_llm_reports))
|
2015-08-27 18:46:26 +03:00
|
|
|
return;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (in_dev->dead)
|
|
|
|
return;
|
2018-08-29 13:06:10 +03:00
|
|
|
|
2022-07-15 20:17:44 +03:00
|
|
|
im->unsolicit_count = READ_ONCE(net->ipv4.sysctl_igmp_qrv);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (IGMP_V1_SEEN(in_dev) || IGMP_V2_SEEN(in_dev)) {
|
|
|
|
spin_lock_bh(&im->lock);
|
2014-11-04 22:52:14 +03:00
|
|
|
igmp_start_timer(im, IGMP_INITIAL_REPORT_DELAY);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_unlock_bh(&im->lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* else, v3 */
|
|
|
|
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
/* Based on RFC3376 5.1, for newly added INCLUDE SSM, we should
|
|
|
|
* not send filter-mode change record as the mode should be from
|
|
|
|
* IN() to IN(A).
|
|
|
|
*/
|
2018-07-20 09:07:42 +03:00
|
|
|
if (im->sfmode == MCAST_EXCLUDE)
|
2022-07-15 20:17:44 +03:00
|
|
|
im->crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv);
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
igmp_ifc_event(in_dev);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Multicast list managers
|
|
|
|
*/
|
|
|
|
|
2013-06-07 19:48:57 +04:00
|
|
|
static u32 ip_mc_hash(const struct ip_mc_list *im)
|
|
|
|
{
|
2013-06-13 01:11:16 +04:00
|
|
|
return hash_32((__force u32)im->multiaddr, MC_HASH_SZ_LOG);
|
2013-06-07 19:48:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void ip_mc_hash_add(struct in_device *in_dev,
|
|
|
|
struct ip_mc_list *im)
|
|
|
|
{
|
|
|
|
struct ip_mc_list __rcu **mc_hash;
|
|
|
|
u32 hash;
|
|
|
|
|
|
|
|
mc_hash = rtnl_dereference(in_dev->mc_hash);
|
|
|
|
if (mc_hash) {
|
|
|
|
hash = ip_mc_hash(im);
|
2013-06-13 01:11:16 +04:00
|
|
|
im->next_hash = mc_hash[hash];
|
2013-06-07 19:48:57 +04:00
|
|
|
rcu_assign_pointer(mc_hash[hash], im);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* do not use a hash table for small number of items */
|
|
|
|
if (in_dev->mc_count < 4)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mc_hash = kzalloc(sizeof(struct ip_mc_list *) << MC_HASH_SZ_LOG,
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!mc_hash)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for_each_pmc_rtnl(in_dev, im) {
|
|
|
|
hash = ip_mc_hash(im);
|
2013-06-13 01:11:16 +04:00
|
|
|
im->next_hash = mc_hash[hash];
|
2013-06-07 19:48:57 +04:00
|
|
|
RCU_INIT_POINTER(mc_hash[hash], im);
|
|
|
|
}
|
|
|
|
|
|
|
|
rcu_assign_pointer(in_dev->mc_hash, mc_hash);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ip_mc_hash_remove(struct in_device *in_dev,
|
|
|
|
struct ip_mc_list *im)
|
|
|
|
{
|
|
|
|
struct ip_mc_list __rcu **mc_hash = rtnl_dereference(in_dev->mc_hash);
|
|
|
|
struct ip_mc_list *aux;
|
|
|
|
|
|
|
|
if (!mc_hash)
|
|
|
|
return;
|
|
|
|
mc_hash += ip_mc_hash(im);
|
|
|
|
while ((aux = rtnl_dereference(*mc_hash)) != im)
|
|
|
|
mc_hash = &aux->next_hash;
|
|
|
|
*mc_hash = im->next_hash;
|
|
|
|
}
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* A socket has joined a multicast group on device dev.
|
|
|
|
*/
|
2019-02-02 07:20:52 +03:00
|
|
|
static void ____ip_mc_inc_group(struct in_device *in_dev, __be32 addr,
|
|
|
|
unsigned int mode, gfp_t gfp)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct ip_mc_list *im;
|
|
|
|
|
|
|
|
ASSERT_RTNL();
|
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
for_each_pmc_rtnl(in_dev, im) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (im->multiaddr == addr) {
|
|
|
|
im->users++;
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
ip_mc_add_src(in_dev, &addr, mode, 0, NULL, 0);
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-02 07:20:52 +03:00
|
|
|
im = kzalloc(sizeof(*im), gfp);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!im)
|
|
|
|
goto out;
|
|
|
|
|
2008-11-03 11:26:09 +03:00
|
|
|
im->users = 1;
|
|
|
|
im->interface = in_dev;
|
2005-04-17 02:20:36 +04:00
|
|
|
in_dev_hold(in_dev);
|
2008-11-03 11:26:09 +03:00
|
|
|
im->multiaddr = addr;
|
2005-04-17 02:20:36 +04:00
|
|
|
/* initial mode is (EX, empty) */
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
im->sfmode = mode;
|
|
|
|
im->sfcount[mode] = 1;
|
2017-06-30 13:08:02 +03:00
|
|
|
refcount_set(&im->refcnt, 1);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock_init(&im->lock);
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
treewide: setup_timer() -> timer_setup()
This converts all remaining cases of the old setup_timer() API into using
timer_setup(), where the callback argument is the structure already
holding the struct timer_list. These should have no behavioral changes,
since they just change which pointer is passed into the callback with
the same available pointers after conversion. It handles the following
examples, in addition to some other variations.
Casting from unsigned long:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
setup_timer(&ptr->my_timer, my_callback, ptr);
and forced object casts:
void my_callback(struct something *ptr)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr);
become:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
Direct function assignments:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
ptr->my_timer.function = my_callback;
have a temporary cast added, along with converting the args:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback;
And finally, callbacks without a data assignment:
void my_callback(unsigned long data)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, 0);
have their argument renamed to verify they're unused during conversion:
void my_callback(struct timer_list *unused)
{
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
The conversion is done with the following Coccinelle script:
spatch --very-quiet --all-includes --include-headers \
-I ./arch/x86/include -I ./arch/x86/include/generated \
-I ./include -I ./arch/x86/include/uapi \
-I ./arch/x86/include/generated/uapi -I ./include/uapi \
-I ./include/generated/uapi --include ./include/linux/kconfig.h \
--dir . \
--cocci-file ~/src/data/timer_setup.cocci
@fix_address_of@
expression e;
@@
setup_timer(
-&(e)
+&e
, ...)
// Update any raw setup_timer() usages that have a NULL callback, but
// would otherwise match change_timer_function_usage, since the latter
// will update all function assignments done in the face of a NULL
// function initialization in setup_timer().
@change_timer_function_usage_NULL@
expression _E;
identifier _timer;
type _cast_data;
@@
(
-setup_timer(&_E->_timer, NULL, _E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E->_timer, NULL, (_cast_data)_E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, &_E);
+timer_setup(&_E._timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, (_cast_data)&_E);
+timer_setup(&_E._timer, NULL, 0);
)
@change_timer_function_usage@
expression _E;
identifier _timer;
struct timer_list _stl;
identifier _callback;
type _cast_func, _cast_data;
@@
(
-setup_timer(&_E->_timer, _callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
_E->_timer@_stl.function = _callback;
|
_E->_timer@_stl.function = &_callback;
|
_E->_timer@_stl.function = (_cast_func)_callback;
|
_E->_timer@_stl.function = (_cast_func)&_callback;
|
_E._timer@_stl.function = _callback;
|
_E._timer@_stl.function = &_callback;
|
_E._timer@_stl.function = (_cast_func)_callback;
|
_E._timer@_stl.function = (_cast_func)&_callback;
)
// callback(unsigned long arg)
@change_callback_handle_cast
depends on change_timer_function_usage@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
identifier _handle;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
(
... when != _origarg
_handletype *_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
)
}
// callback(unsigned long arg) without existing variable
@change_callback_handle_cast_no_arg
depends on change_timer_function_usage &&
!change_callback_handle_cast@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
+ _handletype *_origarg = from_timer(_origarg, t, _timer);
+
... when != _origarg
- (_handletype *)_origarg
+ _origarg
... when != _origarg
}
// Avoid already converted callbacks.
@match_callback_converted
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier t;
@@
void _callback(struct timer_list *t)
{ ... }
// callback(struct something *handle)
@change_callback_handle_arg
depends on change_timer_function_usage &&
!match_callback_converted &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
@@
void _callback(
-_handletype *_handle
+struct timer_list *t
)
{
+ _handletype *_handle = from_timer(_handle, t, _timer);
...
}
// If change_callback_handle_arg ran on an empty function, remove
// the added handler.
@unchange_callback_handle_arg
depends on change_timer_function_usage &&
change_callback_handle_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
identifier t;
@@
void _callback(struct timer_list *t)
{
- _handletype *_handle = from_timer(_handle, t, _timer);
}
// We only want to refactor the setup_timer() data argument if we've found
// the matching callback. This undoes changes in change_timer_function_usage.
@unchange_timer_function_usage
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg &&
!change_callback_handle_arg@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type change_timer_function_usage._cast_data;
@@
(
-timer_setup(&_E->_timer, _callback, 0);
+setup_timer(&_E->_timer, _callback, (_cast_data)_E);
|
-timer_setup(&_E._timer, _callback, 0);
+setup_timer(&_E._timer, _callback, (_cast_data)&_E);
)
// If we fixed a callback from a .function assignment, fix the
// assignment cast now.
@change_timer_function_assignment
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_func;
typedef TIMER_FUNC_TYPE;
@@
(
_E->_timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-&_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
)
// Sometimes timer functions are called directly. Replace matched args.
@change_timer_function_calls
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression _E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_data;
@@
_callback(
(
-(_cast_data)_E
+&_E->_timer
|
-(_cast_data)&_E
+&_E._timer
|
-_E
+&_E->_timer
)
)
// If a timer has been configured without a data argument, it can be
// converted without regard to the callback argument, since it is unused.
@match_timer_function_unused_data@
expression _E;
identifier _timer;
identifier _callback;
@@
(
-setup_timer(&_E->_timer, _callback, 0);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0L);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0UL);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0L);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0UL);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0L);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0UL);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0L);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0UL);
+timer_setup(_timer, _callback, 0);
)
@change_callback_unused_data
depends on match_timer_function_unused_data@
identifier match_timer_function_unused_data._callback;
type _origtype;
identifier _origarg;
@@
void _callback(
-_origtype _origarg
+struct timer_list *unused
)
{
... when != _origarg
}
Signed-off-by: Kees Cook <keescook@chromium.org>
2017-10-17 00:43:17 +03:00
|
|
|
timer_setup(&im->timer, igmp_timer_expire, 0);
|
2005-04-17 02:20:36 +04:00
|
|
|
#endif
|
2010-11-12 08:46:50 +03:00
|
|
|
|
|
|
|
im->next_rcu = in_dev->mc_list;
|
2008-10-08 02:34:37 +04:00
|
|
|
in_dev->mc_count++;
|
2012-01-12 08:41:32 +04:00
|
|
|
rcu_assign_pointer(in_dev->mc_list, im);
|
2010-11-12 08:46:50 +03:00
|
|
|
|
2013-06-07 19:48:57 +04:00
|
|
|
ip_mc_hash_add(in_dev, im);
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
2016-11-14 11:16:28 +03:00
|
|
|
igmpv3_del_delrec(in_dev, im);
|
2005-04-17 02:20:36 +04:00
|
|
|
#endif
|
2018-07-20 09:07:42 +03:00
|
|
|
igmp_group_added(im);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!in_dev->dead)
|
|
|
|
ip_rt_multicast_event(in_dev);
|
|
|
|
out:
|
|
|
|
return;
|
|
|
|
}
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
|
2019-02-02 07:20:52 +03:00
|
|
|
void __ip_mc_inc_group(struct in_device *in_dev, __be32 addr, gfp_t gfp)
|
|
|
|
{
|
|
|
|
____ip_mc_inc_group(in_dev, addr, MCAST_EXCLUDE, gfp);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(__ip_mc_inc_group);
|
|
|
|
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
|
|
|
|
{
|
2019-08-20 08:52:47 +03:00
|
|
|
__ip_mc_inc_group(in_dev, addr, GFP_KERNEL);
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
}
|
2010-07-10 01:22:10 +04:00
|
|
|
EXPORT_SYMBOL(ip_mc_inc_group);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2015-05-02 15:01:07 +03:00
|
|
|
static int ip_mc_check_iphdr(struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
const struct iphdr *iph;
|
|
|
|
unsigned int len;
|
|
|
|
unsigned int offset = skb_network_offset(skb) + sizeof(*iph);
|
|
|
|
|
|
|
|
if (!pskb_may_pull(skb, offset))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
iph = ip_hdr(skb);
|
|
|
|
|
|
|
|
if (iph->version != 4 || ip_hdrlen(skb) < sizeof(*iph))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
offset += ip_hdrlen(skb) - sizeof(*iph);
|
|
|
|
|
|
|
|
if (!pskb_may_pull(skb, offset))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
iph = ip_hdr(skb);
|
|
|
|
|
|
|
|
if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
len = skb_network_offset(skb) + ntohs(iph->tot_len);
|
|
|
|
if (skb->len < len || len < offset)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
skb_set_transport_header(skb, offset);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ip_mc_check_igmp_reportv3(struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
unsigned int len = skb_transport_offset(skb);
|
|
|
|
|
|
|
|
len += sizeof(struct igmpv3_report);
|
|
|
|
|
2019-01-21 09:26:26 +03:00
|
|
|
return ip_mc_may_pull(skb, len) ? 0 : -EINVAL;
|
2015-05-02 15:01:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ip_mc_check_igmp_query(struct sk_buff *skb)
|
|
|
|
{
|
2019-01-21 09:26:26 +03:00
|
|
|
unsigned int transport_len = ip_transport_len(skb);
|
|
|
|
unsigned int len;
|
2015-05-02 15:01:07 +03:00
|
|
|
|
|
|
|
/* IGMPv{1,2}? */
|
2019-01-21 09:26:26 +03:00
|
|
|
if (transport_len != sizeof(struct igmphdr)) {
|
2015-05-02 15:01:07 +03:00
|
|
|
/* or IGMPv3? */
|
2019-01-21 09:26:26 +03:00
|
|
|
if (transport_len < sizeof(struct igmpv3_query))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
len = skb_transport_offset(skb) + sizeof(struct igmpv3_query);
|
|
|
|
if (!ip_mc_may_pull(skb, len))
|
2015-05-02 15:01:07 +03:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* RFC2236+RFC3376 (IGMPv2+IGMPv3) require the multicast link layer
|
|
|
|
* all-systems destination addresses (224.0.0.1) for general queries
|
|
|
|
*/
|
|
|
|
if (!igmp_hdr(skb)->group &&
|
|
|
|
ip_hdr(skb)->daddr != htonl(INADDR_ALLHOSTS_GROUP))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ip_mc_check_igmp_msg(struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
switch (igmp_hdr(skb)->type) {
|
|
|
|
case IGMP_HOST_LEAVE_MESSAGE:
|
|
|
|
case IGMP_HOST_MEMBERSHIP_REPORT:
|
|
|
|
case IGMPV2_HOST_MEMBERSHIP_REPORT:
|
|
|
|
return 0;
|
|
|
|
case IGMPV3_HOST_MEMBERSHIP_REPORT:
|
|
|
|
return ip_mc_check_igmp_reportv3(skb);
|
|
|
|
case IGMP_HOST_MEMBERSHIP_QUERY:
|
|
|
|
return ip_mc_check_igmp_query(skb);
|
|
|
|
default:
|
|
|
|
return -ENOMSG;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-04 00:26:52 +03:00
|
|
|
static __sum16 ip_mc_validate_checksum(struct sk_buff *skb)
|
2015-05-02 15:01:07 +03:00
|
|
|
{
|
|
|
|
return skb_checksum_simple_validate(skb);
|
|
|
|
}
|
|
|
|
|
2019-01-21 09:26:26 +03:00
|
|
|
static int ip_mc_check_igmp_csum(struct sk_buff *skb)
|
2015-05-02 15:01:07 +03:00
|
|
|
{
|
|
|
|
unsigned int len = skb_transport_offset(skb) + sizeof(struct igmphdr);
|
2019-01-21 09:26:26 +03:00
|
|
|
unsigned int transport_len = ip_transport_len(skb);
|
|
|
|
struct sk_buff *skb_chk;
|
2015-05-02 15:01:07 +03:00
|
|
|
|
2019-01-21 09:26:26 +03:00
|
|
|
if (!ip_mc_may_pull(skb, len))
|
|
|
|
return -EINVAL;
|
2015-05-02 15:01:07 +03:00
|
|
|
|
|
|
|
skb_chk = skb_checksum_trimmed(skb, transport_len,
|
|
|
|
ip_mc_validate_checksum);
|
|
|
|
if (!skb_chk)
|
2019-01-21 09:26:26 +03:00
|
|
|
return -EINVAL;
|
2015-08-13 06:54:07 +03:00
|
|
|
|
2019-01-21 09:26:26 +03:00
|
|
|
if (skb_chk != skb)
|
2015-08-13 06:54:07 +03:00
|
|
|
kfree_skb(skb_chk);
|
|
|
|
|
2019-01-21 09:26:26 +03:00
|
|
|
return 0;
|
2015-05-02 15:01:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ip_mc_check_igmp - checks whether this is a sane IGMP packet
|
|
|
|
* @skb: the skb to validate
|
|
|
|
*
|
|
|
|
* Checks whether an IPv4 packet is a valid IGMP packet. If so sets
|
2015-08-13 06:54:07 +03:00
|
|
|
* skb transport header accordingly and returns zero.
|
2015-05-02 15:01:07 +03:00
|
|
|
*
|
|
|
|
* -EINVAL: A broken packet was detected, i.e. it violates some internet
|
|
|
|
* standard
|
|
|
|
* -ENOMSG: IP header validation succeeded but it is not an IGMP packet.
|
|
|
|
* -ENOMEM: A memory allocation failure happened.
|
|
|
|
*
|
2015-08-13 06:54:07 +03:00
|
|
|
* Caller needs to set the skb network header and free any returned skb if it
|
|
|
|
* differs from the provided skb.
|
2015-05-02 15:01:07 +03:00
|
|
|
*/
|
2019-01-21 09:26:25 +03:00
|
|
|
int ip_mc_check_igmp(struct sk_buff *skb)
|
2015-05-02 15:01:07 +03:00
|
|
|
{
|
|
|
|
int ret = ip_mc_check_iphdr(skb);
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (ip_hdr(skb)->protocol != IPPROTO_IGMP)
|
|
|
|
return -ENOMSG;
|
|
|
|
|
2019-01-21 09:26:26 +03:00
|
|
|
ret = ip_mc_check_igmp_csum(skb);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return ip_mc_check_igmp_msg(skb);
|
2015-05-02 15:01:07 +03:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ip_mc_check_igmp);
|
|
|
|
|
bonding: Improve IGMP join processing
In active-backup mode, the current bonding code duplicates IGMP
traffic to all slaves, so that switches are up to date in case of a
failover from an active to a backup interface. If bonding then fails
back to the original active interface, it is likely that the "active
slave" switch's IGMP forwarding for the port will be out of date until
some event occurs to refresh the switch (e.g., a membership query).
This patch alters the behavior of bonding to no longer flood
IGMP to all ports, and to issue IGMP JOINs to the newly active port at
the time of a failover. This insures that switches are kept up to date
for all cases.
"GOELLESCH Niels" <niels.goellesch@eurocontrol.int> originally
reported this problem, and included a patch. His original patch was
modified by Jay Vosburgh to additionally remove the existing IGMP flood
behavior, use RCU, streamline code paths, fix trailing white space, and
adjust for style.
Signed-off-by: Jay Vosburgh <fubar@us.ibm.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
2007-03-01 04:03:37 +03:00
|
|
|
/*
|
2013-07-20 14:13:53 +04:00
|
|
|
* Resend IGMP JOIN report; used by netdev notifier.
|
bonding: Improve IGMP join processing
In active-backup mode, the current bonding code duplicates IGMP
traffic to all slaves, so that switches are up to date in case of a
failover from an active to a backup interface. If bonding then fails
back to the original active interface, it is likely that the "active
slave" switch's IGMP forwarding for the port will be out of date until
some event occurs to refresh the switch (e.g., a membership query).
This patch alters the behavior of bonding to no longer flood
IGMP to all ports, and to issue IGMP JOINs to the newly active port at
the time of a failover. This insures that switches are kept up to date
for all cases.
"GOELLESCH Niels" <niels.goellesch@eurocontrol.int> originally
reported this problem, and included a patch. His original patch was
modified by Jay Vosburgh to additionally remove the existing IGMP flood
behavior, use RCU, streamline code paths, fix trailing white space, and
adjust for style.
Signed-off-by: Jay Vosburgh <fubar@us.ibm.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
2007-03-01 04:03:37 +03:00
|
|
|
*/
|
2013-07-20 14:13:53 +04:00
|
|
|
static void ip_mc_rejoin_groups(struct in_device *in_dev)
|
bonding: Improve IGMP join processing
In active-backup mode, the current bonding code duplicates IGMP
traffic to all slaves, so that switches are up to date in case of a
failover from an active to a backup interface. If bonding then fails
back to the original active interface, it is likely that the "active
slave" switch's IGMP forwarding for the port will be out of date until
some event occurs to refresh the switch (e.g., a membership query).
This patch alters the behavior of bonding to no longer flood
IGMP to all ports, and to issue IGMP JOINs to the newly active port at
the time of a failover. This insures that switches are kept up to date
for all cases.
"GOELLESCH Niels" <niels.goellesch@eurocontrol.int> originally
reported this problem, and included a patch. His original patch was
modified by Jay Vosburgh to additionally remove the existing IGMP flood
behavior, use RCU, streamline code paths, fix trailing white space, and
adjust for style.
Signed-off-by: Jay Vosburgh <fubar@us.ibm.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
2007-03-01 04:03:37 +03:00
|
|
|
{
|
2007-03-13 03:02:37 +03:00
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
2010-11-18 20:33:19 +03:00
|
|
|
struct ip_mc_list *im;
|
|
|
|
int type;
|
2016-02-09 01:13:50 +03:00
|
|
|
struct net *net = dev_net(in_dev->dev);
|
bonding: Improve IGMP join processing
In active-backup mode, the current bonding code duplicates IGMP
traffic to all slaves, so that switches are up to date in case of a
failover from an active to a backup interface. If bonding then fails
back to the original active interface, it is likely that the "active
slave" switch's IGMP forwarding for the port will be out of date until
some event occurs to refresh the switch (e.g., a membership query).
This patch alters the behavior of bonding to no longer flood
IGMP to all ports, and to issue IGMP JOINs to the newly active port at
the time of a failover. This insures that switches are kept up to date
for all cases.
"GOELLESCH Niels" <niels.goellesch@eurocontrol.int> originally
reported this problem, and included a patch. His original patch was
modified by Jay Vosburgh to additionally remove the existing IGMP flood
behavior, use RCU, streamline code paths, fix trailing white space, and
adjust for style.
Signed-off-by: Jay Vosburgh <fubar@us.ibm.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
2007-03-01 04:03:37 +03:00
|
|
|
|
2013-07-20 14:13:53 +04:00
|
|
|
ASSERT_RTNL();
|
|
|
|
|
|
|
|
for_each_pmc_rtnl(in_dev, im) {
|
2010-11-18 20:33:19 +03:00
|
|
|
if (im->multiaddr == IGMP_ALL_HOSTS)
|
|
|
|
continue;
|
2015-08-27 18:46:26 +03:00
|
|
|
if (ipv4_is_local_multicast(im->multiaddr) &&
|
2022-07-15 20:17:41 +03:00
|
|
|
!READ_ONCE(net->ipv4.sysctl_igmp_llm_reports))
|
2015-08-27 18:46:26 +03:00
|
|
|
continue;
|
bonding: Improve IGMP join processing
In active-backup mode, the current bonding code duplicates IGMP
traffic to all slaves, so that switches are up to date in case of a
failover from an active to a backup interface. If bonding then fails
back to the original active interface, it is likely that the "active
slave" switch's IGMP forwarding for the port will be out of date until
some event occurs to refresh the switch (e.g., a membership query).
This patch alters the behavior of bonding to no longer flood
IGMP to all ports, and to issue IGMP JOINs to the newly active port at
the time of a failover. This insures that switches are kept up to date
for all cases.
"GOELLESCH Niels" <niels.goellesch@eurocontrol.int> originally
reported this problem, and included a patch. His original patch was
modified by Jay Vosburgh to additionally remove the existing IGMP flood
behavior, use RCU, streamline code paths, fix trailing white space, and
adjust for style.
Signed-off-by: Jay Vosburgh <fubar@us.ibm.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
2007-03-01 04:03:37 +03:00
|
|
|
|
2010-11-18 20:33:19 +03:00
|
|
|
/* a failover is happening and switches
|
|
|
|
* must be notified immediately
|
|
|
|
*/
|
|
|
|
if (IGMP_V1_SEEN(in_dev))
|
|
|
|
type = IGMP_HOST_MEMBERSHIP_REPORT;
|
|
|
|
else if (IGMP_V2_SEEN(in_dev))
|
|
|
|
type = IGMPV2_HOST_MEMBERSHIP_REPORT;
|
|
|
|
else
|
|
|
|
type = IGMPV3_HOST_MEMBERSHIP_REPORT;
|
|
|
|
igmp_send_report(in_dev, im, type);
|
|
|
|
}
|
bonding: Improve IGMP join processing
In active-backup mode, the current bonding code duplicates IGMP
traffic to all slaves, so that switches are up to date in case of a
failover from an active to a backup interface. If bonding then fails
back to the original active interface, it is likely that the "active
slave" switch's IGMP forwarding for the port will be out of date until
some event occurs to refresh the switch (e.g., a membership query).
This patch alters the behavior of bonding to no longer flood
IGMP to all ports, and to issue IGMP JOINs to the newly active port at
the time of a failover. This insures that switches are kept up to date
for all cases.
"GOELLESCH Niels" <niels.goellesch@eurocontrol.int> originally
reported this problem, and included a patch. His original patch was
modified by Jay Vosburgh to additionally remove the existing IGMP flood
behavior, use RCU, streamline code paths, fix trailing white space, and
adjust for style.
Signed-off-by: Jay Vosburgh <fubar@us.ibm.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
2007-03-01 04:03:37 +03:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
/*
|
|
|
|
* A socket has left a multicast group on device dev
|
|
|
|
*/
|
|
|
|
|
2019-02-02 07:20:52 +03:00
|
|
|
void __ip_mc_dec_group(struct in_device *in_dev, __be32 addr, gfp_t gfp)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2010-11-12 08:46:50 +03:00
|
|
|
struct ip_mc_list *i;
|
|
|
|
struct ip_mc_list __rcu **ip;
|
2007-02-09 17:24:47 +03:00
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
ASSERT_RTNL();
|
2007-02-09 17:24:47 +03:00
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
for (ip = &in_dev->mc_list;
|
|
|
|
(i = rtnl_dereference(*ip)) != NULL;
|
|
|
|
ip = &i->next_rcu) {
|
2008-11-03 11:26:09 +03:00
|
|
|
if (i->multiaddr == addr) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (--i->users == 0) {
|
2013-06-07 19:48:57 +04:00
|
|
|
ip_mc_hash_remove(in_dev, i);
|
2010-11-12 08:46:50 +03:00
|
|
|
*ip = i->next_rcu;
|
2008-10-08 02:34:37 +04:00
|
|
|
in_dev->mc_count--;
|
2019-02-02 07:20:52 +03:00
|
|
|
__igmp_group_dropped(i, gfp);
|
2011-05-24 03:15:05 +04:00
|
|
|
ip_mc_clear_src(i);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (!in_dev->dead)
|
|
|
|
ip_rt_multicast_event(in_dev);
|
|
|
|
|
|
|
|
ip_ma_put(i);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-02 07:20:52 +03:00
|
|
|
EXPORT_SYMBOL(__ip_mc_dec_group);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2009-09-15 13:37:40 +04:00
|
|
|
/* Device changing type */
|
|
|
|
|
|
|
|
void ip_mc_unmap(struct in_device *in_dev)
|
|
|
|
{
|
2010-11-12 08:46:50 +03:00
|
|
|
struct ip_mc_list *pmc;
|
2009-09-15 13:37:40 +04:00
|
|
|
|
|
|
|
ASSERT_RTNL();
|
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
for_each_pmc_rtnl(in_dev, pmc)
|
|
|
|
igmp_group_dropped(pmc);
|
2009-09-15 13:37:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void ip_mc_remap(struct in_device *in_dev)
|
|
|
|
{
|
2010-11-12 08:46:50 +03:00
|
|
|
struct ip_mc_list *pmc;
|
2009-09-15 13:37:40 +04:00
|
|
|
|
|
|
|
ASSERT_RTNL();
|
|
|
|
|
2016-11-14 11:16:28 +03:00
|
|
|
for_each_pmc_rtnl(in_dev, pmc) {
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
igmpv3_del_delrec(in_dev, pmc);
|
|
|
|
#endif
|
2018-07-20 09:07:42 +03:00
|
|
|
igmp_group_added(pmc);
|
2016-11-14 11:16:28 +03:00
|
|
|
}
|
2009-09-15 13:37:40 +04:00
|
|
|
}
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
/* Device going down */
|
|
|
|
|
|
|
|
void ip_mc_down(struct in_device *in_dev)
|
|
|
|
{
|
2010-11-12 08:46:50 +03:00
|
|
|
struct ip_mc_list *pmc;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
ASSERT_RTNL();
|
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
for_each_pmc_rtnl(in_dev, pmc)
|
|
|
|
igmp_group_dropped(pmc);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
2021-08-10 12:45:47 +03:00
|
|
|
WRITE_ONCE(in_dev->mr_ifc_count, 0);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (del_timer(&in_dev->mr_ifc_timer))
|
|
|
|
__in_dev_put(in_dev);
|
|
|
|
in_dev->mr_gq_running = 0;
|
|
|
|
if (del_timer(&in_dev->mr_gq_timer))
|
|
|
|
__in_dev_put(in_dev);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
ip_mc_dec_group(in_dev, IGMP_ALL_HOSTS);
|
|
|
|
}
|
|
|
|
|
2016-02-15 13:11:28 +03:00
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
2018-10-26 06:30:35 +03:00
|
|
|
static void ip_mc_reset(struct in_device *in_dev)
|
|
|
|
{
|
2016-02-09 00:29:24 +03:00
|
|
|
struct net *net = dev_net(in_dev->dev);
|
2018-10-26 06:30:35 +03:00
|
|
|
|
|
|
|
in_dev->mr_qi = IGMP_QUERY_INTERVAL;
|
|
|
|
in_dev->mr_qri = IGMP_QUERY_RESPONSE_INTERVAL;
|
2022-07-15 20:17:44 +03:00
|
|
|
in_dev->mr_qrv = READ_ONCE(net->ipv4.sysctl_igmp_qrv);
|
2018-10-26 06:30:35 +03:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
static void ip_mc_reset(struct in_device *in_dev)
|
|
|
|
{
|
|
|
|
}
|
2016-02-15 13:11:28 +03:00
|
|
|
#endif
|
2018-10-26 06:30:35 +03:00
|
|
|
|
|
|
|
void ip_mc_init_dev(struct in_device *in_dev)
|
|
|
|
{
|
2005-04-17 02:20:36 +04:00
|
|
|
ASSERT_RTNL();
|
|
|
|
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
treewide: setup_timer() -> timer_setup()
This converts all remaining cases of the old setup_timer() API into using
timer_setup(), where the callback argument is the structure already
holding the struct timer_list. These should have no behavioral changes,
since they just change which pointer is passed into the callback with
the same available pointers after conversion. It handles the following
examples, in addition to some other variations.
Casting from unsigned long:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
setup_timer(&ptr->my_timer, my_callback, ptr);
and forced object casts:
void my_callback(struct something *ptr)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr);
become:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
Direct function assignments:
void my_callback(unsigned long data)
{
struct something *ptr = (struct something *)data;
...
}
...
ptr->my_timer.function = my_callback;
have a temporary cast added, along with converting the args:
void my_callback(struct timer_list *t)
{
struct something *ptr = from_timer(ptr, t, my_timer);
...
}
...
ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback;
And finally, callbacks without a data assignment:
void my_callback(unsigned long data)
{
...
}
...
setup_timer(&ptr->my_timer, my_callback, 0);
have their argument renamed to verify they're unused during conversion:
void my_callback(struct timer_list *unused)
{
...
}
...
timer_setup(&ptr->my_timer, my_callback, 0);
The conversion is done with the following Coccinelle script:
spatch --very-quiet --all-includes --include-headers \
-I ./arch/x86/include -I ./arch/x86/include/generated \
-I ./include -I ./arch/x86/include/uapi \
-I ./arch/x86/include/generated/uapi -I ./include/uapi \
-I ./include/generated/uapi --include ./include/linux/kconfig.h \
--dir . \
--cocci-file ~/src/data/timer_setup.cocci
@fix_address_of@
expression e;
@@
setup_timer(
-&(e)
+&e
, ...)
// Update any raw setup_timer() usages that have a NULL callback, but
// would otherwise match change_timer_function_usage, since the latter
// will update all function assignments done in the face of a NULL
// function initialization in setup_timer().
@change_timer_function_usage_NULL@
expression _E;
identifier _timer;
type _cast_data;
@@
(
-setup_timer(&_E->_timer, NULL, _E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E->_timer, NULL, (_cast_data)_E);
+timer_setup(&_E->_timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, &_E);
+timer_setup(&_E._timer, NULL, 0);
|
-setup_timer(&_E._timer, NULL, (_cast_data)&_E);
+timer_setup(&_E._timer, NULL, 0);
)
@change_timer_function_usage@
expression _E;
identifier _timer;
struct timer_list _stl;
identifier _callback;
type _cast_func, _cast_data;
@@
(
-setup_timer(&_E->_timer, _callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, &_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, _E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, &_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E);
+timer_setup(&_E._timer, _callback, 0);
|
_E->_timer@_stl.function = _callback;
|
_E->_timer@_stl.function = &_callback;
|
_E->_timer@_stl.function = (_cast_func)_callback;
|
_E->_timer@_stl.function = (_cast_func)&_callback;
|
_E._timer@_stl.function = _callback;
|
_E._timer@_stl.function = &_callback;
|
_E._timer@_stl.function = (_cast_func)_callback;
|
_E._timer@_stl.function = (_cast_func)&_callback;
)
// callback(unsigned long arg)
@change_callback_handle_cast
depends on change_timer_function_usage@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
identifier _handle;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
(
... when != _origarg
_handletype *_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(_handletype *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
|
... when != _origarg
_handletype *_handle;
... when != _handle
_handle =
-(void *)_origarg;
+from_timer(_handle, t, _timer);
... when != _origarg
)
}
// callback(unsigned long arg) without existing variable
@change_callback_handle_cast_no_arg
depends on change_timer_function_usage &&
!change_callback_handle_cast@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _origtype;
identifier _origarg;
type _handletype;
@@
void _callback(
-_origtype _origarg
+struct timer_list *t
)
{
+ _handletype *_origarg = from_timer(_origarg, t, _timer);
+
... when != _origarg
- (_handletype *)_origarg
+ _origarg
... when != _origarg
}
// Avoid already converted callbacks.
@match_callback_converted
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier t;
@@
void _callback(struct timer_list *t)
{ ... }
// callback(struct something *handle)
@change_callback_handle_arg
depends on change_timer_function_usage &&
!match_callback_converted &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
@@
void _callback(
-_handletype *_handle
+struct timer_list *t
)
{
+ _handletype *_handle = from_timer(_handle, t, _timer);
...
}
// If change_callback_handle_arg ran on an empty function, remove
// the added handler.
@unchange_callback_handle_arg
depends on change_timer_function_usage &&
change_callback_handle_arg@
identifier change_timer_function_usage._callback;
identifier change_timer_function_usage._timer;
type _handletype;
identifier _handle;
identifier t;
@@
void _callback(struct timer_list *t)
{
- _handletype *_handle = from_timer(_handle, t, _timer);
}
// We only want to refactor the setup_timer() data argument if we've found
// the matching callback. This undoes changes in change_timer_function_usage.
@unchange_timer_function_usage
depends on change_timer_function_usage &&
!change_callback_handle_cast &&
!change_callback_handle_cast_no_arg &&
!change_callback_handle_arg@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type change_timer_function_usage._cast_data;
@@
(
-timer_setup(&_E->_timer, _callback, 0);
+setup_timer(&_E->_timer, _callback, (_cast_data)_E);
|
-timer_setup(&_E._timer, _callback, 0);
+setup_timer(&_E._timer, _callback, (_cast_data)&_E);
)
// If we fixed a callback from a .function assignment, fix the
// assignment cast now.
@change_timer_function_assignment
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression change_timer_function_usage._E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_func;
typedef TIMER_FUNC_TYPE;
@@
(
_E->_timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E->_timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-&_callback;
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)_callback
+(TIMER_FUNC_TYPE)_callback
;
|
_E._timer.function =
-(_cast_func)&_callback
+(TIMER_FUNC_TYPE)_callback
;
)
// Sometimes timer functions are called directly. Replace matched args.
@change_timer_function_calls
depends on change_timer_function_usage &&
(change_callback_handle_cast ||
change_callback_handle_cast_no_arg ||
change_callback_handle_arg)@
expression _E;
identifier change_timer_function_usage._timer;
identifier change_timer_function_usage._callback;
type _cast_data;
@@
_callback(
(
-(_cast_data)_E
+&_E->_timer
|
-(_cast_data)&_E
+&_E._timer
|
-_E
+&_E->_timer
)
)
// If a timer has been configured without a data argument, it can be
// converted without regard to the callback argument, since it is unused.
@match_timer_function_unused_data@
expression _E;
identifier _timer;
identifier _callback;
@@
(
-setup_timer(&_E->_timer, _callback, 0);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0L);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E->_timer, _callback, 0UL);
+timer_setup(&_E->_timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0L);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_E._timer, _callback, 0UL);
+timer_setup(&_E._timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0L);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(&_timer, _callback, 0UL);
+timer_setup(&_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0L);
+timer_setup(_timer, _callback, 0);
|
-setup_timer(_timer, _callback, 0UL);
+timer_setup(_timer, _callback, 0);
)
@change_callback_unused_data
depends on match_timer_function_unused_data@
identifier match_timer_function_unused_data._callback;
type _origtype;
identifier _origarg;
@@
void _callback(
-_origtype _origarg
+struct timer_list *unused
)
{
... when != _origarg
}
Signed-off-by: Kees Cook <keescook@chromium.org>
2017-10-17 00:43:17 +03:00
|
|
|
timer_setup(&in_dev->mr_gq_timer, igmp_gq_timer_expire, 0);
|
|
|
|
timer_setup(&in_dev->mr_ifc_timer, igmp_ifc_timer_expire, 0);
|
2005-04-17 02:20:36 +04:00
|
|
|
#endif
|
2018-10-26 06:30:35 +03:00
|
|
|
ip_mc_reset(in_dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
spin_lock_init(&in_dev->mc_tomb_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Device going up */
|
|
|
|
|
|
|
|
void ip_mc_up(struct in_device *in_dev)
|
|
|
|
{
|
2010-11-12 08:46:50 +03:00
|
|
|
struct ip_mc_list *pmc;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
ASSERT_RTNL();
|
|
|
|
|
2018-10-26 06:30:35 +03:00
|
|
|
ip_mc_reset(in_dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
ip_mc_inc_group(in_dev, IGMP_ALL_HOSTS);
|
|
|
|
|
2016-11-14 11:16:28 +03:00
|
|
|
for_each_pmc_rtnl(in_dev, pmc) {
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
igmpv3_del_delrec(in_dev, pmc);
|
|
|
|
#endif
|
2018-07-20 09:07:42 +03:00
|
|
|
igmp_group_added(pmc);
|
2016-11-14 11:16:28 +03:00
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Device is about to be destroyed: clean up.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void ip_mc_destroy_dev(struct in_device *in_dev)
|
|
|
|
{
|
|
|
|
struct ip_mc_list *i;
|
|
|
|
|
|
|
|
ASSERT_RTNL();
|
|
|
|
|
|
|
|
/* Deactivate timers */
|
|
|
|
ip_mc_down(in_dev);
|
2016-11-14 11:16:28 +03:00
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
igmpv3_clear_delrec(in_dev);
|
|
|
|
#endif
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
while ((i = rtnl_dereference(in_dev->mc_list)) != NULL) {
|
|
|
|
in_dev->mc_list = i->next_rcu;
|
2008-10-08 02:34:37 +04:00
|
|
|
in_dev->mc_count--;
|
2021-06-16 12:59:25 +03:00
|
|
|
ip_mc_clear_src(i);
|
2005-04-17 02:20:36 +04:00
|
|
|
ip_ma_put(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-10-19 04:39:18 +04:00
|
|
|
/* RTNL is locked */
|
2008-08-14 03:15:57 +04:00
|
|
|
static struct in_device *ip_mc_find_dev(struct net *net, struct ip_mreqn *imr)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct net_device *dev = NULL;
|
|
|
|
struct in_device *idev = NULL;
|
|
|
|
|
|
|
|
if (imr->imr_ifindex) {
|
2008-08-14 03:15:57 +04:00
|
|
|
idev = inetdev_by_index(net, imr->imr_ifindex);
|
2005-04-17 02:20:36 +04:00
|
|
|
return idev;
|
|
|
|
}
|
|
|
|
if (imr->imr_address.s_addr) {
|
2010-10-19 04:39:18 +04:00
|
|
|
dev = __ip_dev_find(net, imr->imr_address.s_addr, false);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!dev)
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2011-03-03 01:31:35 +03:00
|
|
|
if (!dev) {
|
2011-03-12 08:00:52 +03:00
|
|
|
struct rtable *rt = ip_route_output(net,
|
|
|
|
imr->imr_multiaddr.s_addr,
|
|
|
|
0, 0, 0);
|
2011-03-03 01:31:35 +03:00
|
|
|
if (!IS_ERR(rt)) {
|
|
|
|
dev = rt->dst.dev;
|
|
|
|
ip_rt_put(rt);
|
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
if (dev) {
|
|
|
|
imr->imr_ifindex = dev->ifindex;
|
2005-10-04 01:35:55 +04:00
|
|
|
idev = __in_dev_get_rtnl(dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
return idev;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Join a socket to a group
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int ip_mc_del1_src(struct ip_mc_list *pmc, int sfmode,
|
2006-09-28 05:30:07 +04:00
|
|
|
__be32 *psfsrc)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct ip_sf_list *psf, *psf_prev;
|
|
|
|
int rv = 0;
|
|
|
|
|
|
|
|
psf_prev = NULL;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = pmc->sources; psf; psf = psf->sf_next) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (psf->sf_inaddr == *psfsrc)
|
|
|
|
break;
|
|
|
|
psf_prev = psf;
|
|
|
|
}
|
|
|
|
if (!psf || psf->sf_count[sfmode] == 0) {
|
|
|
|
/* source filter not found, or count wrong => bug */
|
|
|
|
return -ESRCH;
|
|
|
|
}
|
|
|
|
psf->sf_count[sfmode]--;
|
|
|
|
if (psf->sf_count[sfmode] == 0) {
|
|
|
|
ip_rt_multicast_event(pmc->interface);
|
|
|
|
}
|
|
|
|
if (!psf->sf_count[MCAST_INCLUDE] && !psf->sf_count[MCAST_EXCLUDE]) {
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
struct in_device *in_dev = pmc->interface;
|
2016-02-09 00:29:24 +03:00
|
|
|
struct net *net = dev_net(in_dev->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/* no more filters for this source */
|
|
|
|
if (psf_prev)
|
|
|
|
psf_prev->sf_next = psf->sf_next;
|
|
|
|
else
|
|
|
|
pmc->sources = psf->sf_next;
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
if (psf->sf_oldin &&
|
|
|
|
!IGMP_V1_SEEN(in_dev) && !IGMP_V2_SEEN(in_dev)) {
|
2022-07-15 20:17:44 +03:00
|
|
|
psf->sf_crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv);
|
2005-04-17 02:20:36 +04:00
|
|
|
psf->sf_next = pmc->tomb;
|
|
|
|
pmc->tomb = psf;
|
|
|
|
rv = 1;
|
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
kfree(psf);
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef CONFIG_IP_MULTICAST
|
|
|
|
#define igmp_ifc_event(x) do { } while (0)
|
|
|
|
#endif
|
|
|
|
|
2006-09-28 05:30:07 +04:00
|
|
|
static int ip_mc_del_src(struct in_device *in_dev, __be32 *pmca, int sfmode,
|
|
|
|
int sfcount, __be32 *psfsrc, int delta)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct ip_mc_list *pmc;
|
|
|
|
int changerec = 0;
|
|
|
|
int i, err;
|
|
|
|
|
|
|
|
if (!in_dev)
|
|
|
|
return -ENODEV;
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_lock();
|
|
|
|
for_each_pmc_rcu(in_dev, pmc) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (*pmca == pmc->multiaddr)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!pmc) {
|
|
|
|
/* MCA not found?? bug */
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_unlock();
|
2005-04-17 02:20:36 +04:00
|
|
|
return -ESRCH;
|
|
|
|
}
|
|
|
|
spin_lock_bh(&pmc->lock);
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_unlock();
|
2005-04-17 02:20:36 +04:00
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
sf_markstate(pmc);
|
|
|
|
#endif
|
|
|
|
if (!delta) {
|
|
|
|
err = -EINVAL;
|
|
|
|
if (!pmc->sfcount[sfmode])
|
|
|
|
goto out_unlock;
|
|
|
|
pmc->sfcount[sfmode]--;
|
|
|
|
}
|
|
|
|
err = 0;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (i = 0; i < sfcount; i++) {
|
2005-04-17 02:20:36 +04:00
|
|
|
int rv = ip_mc_del1_src(pmc, sfmode, &psfsrc[i]);
|
|
|
|
|
|
|
|
changerec |= rv > 0;
|
|
|
|
if (!err && rv < 0)
|
|
|
|
err = rv;
|
|
|
|
}
|
|
|
|
if (pmc->sfmode == MCAST_EXCLUDE &&
|
|
|
|
pmc->sfcount[MCAST_EXCLUDE] == 0 &&
|
|
|
|
pmc->sfcount[MCAST_INCLUDE]) {
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
struct ip_sf_list *psf;
|
2016-02-09 00:29:24 +03:00
|
|
|
struct net *net = dev_net(in_dev->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/* filter mode change */
|
|
|
|
pmc->sfmode = MCAST_INCLUDE;
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
2022-07-15 20:17:44 +03:00
|
|
|
pmc->crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv);
|
2021-08-10 12:45:47 +03:00
|
|
|
WRITE_ONCE(in_dev->mr_ifc_count, pmc->crcount);
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = pmc->sources; psf; psf = psf->sf_next)
|
2005-04-17 02:20:36 +04:00
|
|
|
psf->sf_crcount = 0;
|
|
|
|
igmp_ifc_event(pmc->interface);
|
|
|
|
} else if (sf_setstate(pmc) || changerec) {
|
|
|
|
igmp_ifc_event(pmc->interface);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
out_unlock:
|
|
|
|
spin_unlock_bh(&pmc->lock);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add multicast single-source filter to the interface list
|
|
|
|
*/
|
|
|
|
static int ip_mc_add1_src(struct ip_mc_list *pmc, int sfmode,
|
2011-11-30 10:21:04 +04:00
|
|
|
__be32 *psfsrc)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct ip_sf_list *psf, *psf_prev;
|
|
|
|
|
|
|
|
psf_prev = NULL;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = pmc->sources; psf; psf = psf->sf_next) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (psf->sf_inaddr == *psfsrc)
|
|
|
|
break;
|
|
|
|
psf_prev = psf;
|
|
|
|
}
|
|
|
|
if (!psf) {
|
2006-07-22 01:51:30 +04:00
|
|
|
psf = kzalloc(sizeof(*psf), GFP_ATOMIC);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!psf)
|
|
|
|
return -ENOBUFS;
|
|
|
|
psf->sf_inaddr = *psfsrc;
|
|
|
|
if (psf_prev) {
|
|
|
|
psf_prev->sf_next = psf;
|
|
|
|
} else
|
|
|
|
pmc->sources = psf;
|
|
|
|
}
|
|
|
|
psf->sf_count[sfmode]++;
|
|
|
|
if (psf->sf_count[sfmode] == 1) {
|
|
|
|
ip_rt_multicast_event(pmc->interface);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
static void sf_markstate(struct ip_mc_list *pmc)
|
|
|
|
{
|
|
|
|
struct ip_sf_list *psf;
|
|
|
|
int mca_xcount = pmc->sfcount[MCAST_EXCLUDE];
|
|
|
|
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = pmc->sources; psf; psf = psf->sf_next)
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc->sfcount[MCAST_EXCLUDE]) {
|
|
|
|
psf->sf_oldin = mca_xcount ==
|
|
|
|
psf->sf_count[MCAST_EXCLUDE] &&
|
|
|
|
!psf->sf_count[MCAST_INCLUDE];
|
|
|
|
} else
|
|
|
|
psf->sf_oldin = psf->sf_count[MCAST_INCLUDE] != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sf_setstate(struct ip_mc_list *pmc)
|
|
|
|
{
|
2006-01-19 01:20:56 +03:00
|
|
|
struct ip_sf_list *psf, *dpsf;
|
2005-04-17 02:20:36 +04:00
|
|
|
int mca_xcount = pmc->sfcount[MCAST_EXCLUDE];
|
|
|
|
int qrv = pmc->interface->mr_qrv;
|
|
|
|
int new_in, rv;
|
|
|
|
|
|
|
|
rv = 0;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = pmc->sources; psf; psf = psf->sf_next) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc->sfcount[MCAST_EXCLUDE]) {
|
|
|
|
new_in = mca_xcount == psf->sf_count[MCAST_EXCLUDE] &&
|
|
|
|
!psf->sf_count[MCAST_INCLUDE];
|
|
|
|
} else
|
|
|
|
new_in = psf->sf_count[MCAST_INCLUDE] != 0;
|
2006-01-19 01:20:56 +03:00
|
|
|
if (new_in) {
|
|
|
|
if (!psf->sf_oldin) {
|
2006-02-01 13:54:35 +03:00
|
|
|
struct ip_sf_list *prev = NULL;
|
2006-01-19 01:20:56 +03:00
|
|
|
|
2013-12-23 10:37:29 +04:00
|
|
|
for (dpsf = pmc->tomb; dpsf; dpsf = dpsf->sf_next) {
|
2006-01-19 01:20:56 +03:00
|
|
|
if (dpsf->sf_inaddr == psf->sf_inaddr)
|
|
|
|
break;
|
|
|
|
prev = dpsf;
|
|
|
|
}
|
|
|
|
if (dpsf) {
|
|
|
|
if (prev)
|
|
|
|
prev->sf_next = dpsf->sf_next;
|
|
|
|
else
|
|
|
|
pmc->tomb = dpsf->sf_next;
|
|
|
|
kfree(dpsf);
|
|
|
|
}
|
|
|
|
psf->sf_crcount = qrv;
|
|
|
|
rv++;
|
|
|
|
}
|
|
|
|
} else if (psf->sf_oldin) {
|
|
|
|
|
|
|
|
psf->sf_crcount = 0;
|
|
|
|
/*
|
|
|
|
* add or update "delete" records if an active filter
|
|
|
|
* is now inactive
|
|
|
|
*/
|
2013-12-23 10:37:29 +04:00
|
|
|
for (dpsf = pmc->tomb; dpsf; dpsf = dpsf->sf_next)
|
2006-01-19 01:20:56 +03:00
|
|
|
if (dpsf->sf_inaddr == psf->sf_inaddr)
|
|
|
|
break;
|
|
|
|
if (!dpsf) {
|
2010-05-31 21:23:21 +04:00
|
|
|
dpsf = kmalloc(sizeof(*dpsf), GFP_ATOMIC);
|
2006-01-19 01:20:56 +03:00
|
|
|
if (!dpsf)
|
|
|
|
continue;
|
|
|
|
*dpsf = *psf;
|
|
|
|
/* pmc->lock held by callers */
|
|
|
|
dpsf->sf_next = pmc->tomb;
|
|
|
|
pmc->tomb = dpsf;
|
|
|
|
}
|
|
|
|
dpsf->sf_crcount = qrv;
|
2005-04-17 02:20:36 +04:00
|
|
|
rv++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add multicast source filter list to the interface list
|
|
|
|
*/
|
2006-09-28 05:30:07 +04:00
|
|
|
static int ip_mc_add_src(struct in_device *in_dev, __be32 *pmca, int sfmode,
|
|
|
|
int sfcount, __be32 *psfsrc, int delta)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct ip_mc_list *pmc;
|
|
|
|
int isexclude;
|
|
|
|
int i, err;
|
|
|
|
|
|
|
|
if (!in_dev)
|
|
|
|
return -ENODEV;
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_lock();
|
|
|
|
for_each_pmc_rcu(in_dev, pmc) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (*pmca == pmc->multiaddr)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!pmc) {
|
|
|
|
/* MCA not found?? bug */
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_unlock();
|
2005-04-17 02:20:36 +04:00
|
|
|
return -ESRCH;
|
|
|
|
}
|
|
|
|
spin_lock_bh(&pmc->lock);
|
2010-11-12 08:46:50 +03:00
|
|
|
rcu_read_unlock();
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
sf_markstate(pmc);
|
|
|
|
#endif
|
|
|
|
isexclude = pmc->sfmode == MCAST_EXCLUDE;
|
|
|
|
if (!delta)
|
|
|
|
pmc->sfcount[sfmode]++;
|
|
|
|
err = 0;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (i = 0; i < sfcount; i++) {
|
2011-11-30 10:21:04 +04:00
|
|
|
err = ip_mc_add1_src(pmc, sfmode, &psfsrc[i]);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (err)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (err) {
|
|
|
|
int j;
|
|
|
|
|
2011-11-22 21:19:03 +04:00
|
|
|
if (!delta)
|
|
|
|
pmc->sfcount[sfmode]--;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (j = 0; j < i; j++)
|
2011-07-28 06:46:01 +04:00
|
|
|
(void) ip_mc_del1_src(pmc, sfmode, &psfsrc[j]);
|
2005-04-17 02:20:36 +04:00
|
|
|
} else if (isexclude != (pmc->sfcount[MCAST_EXCLUDE] != 0)) {
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
struct ip_sf_list *psf;
|
2016-02-09 00:29:24 +03:00
|
|
|
struct net *net = dev_net(pmc->interface->dev);
|
2007-10-09 12:59:42 +04:00
|
|
|
in_dev = pmc->interface;
|
2005-04-17 02:20:36 +04:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/* filter mode change */
|
|
|
|
if (pmc->sfcount[MCAST_EXCLUDE])
|
|
|
|
pmc->sfmode = MCAST_EXCLUDE;
|
|
|
|
else if (pmc->sfcount[MCAST_INCLUDE])
|
|
|
|
pmc->sfmode = MCAST_INCLUDE;
|
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
/* else no filters; keep old mode for reports */
|
|
|
|
|
2022-07-15 20:17:44 +03:00
|
|
|
pmc->crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv);
|
2021-08-10 12:45:47 +03:00
|
|
|
WRITE_ONCE(in_dev->mr_ifc_count, pmc->crcount);
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = pmc->sources; psf; psf = psf->sf_next)
|
2005-04-17 02:20:36 +04:00
|
|
|
psf->sf_crcount = 0;
|
|
|
|
igmp_ifc_event(in_dev);
|
|
|
|
} else if (sf_setstate(pmc)) {
|
|
|
|
igmp_ifc_event(in_dev);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
spin_unlock_bh(&pmc->lock);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ip_mc_clear_src(struct ip_mc_list *pmc)
|
|
|
|
{
|
2019-05-23 02:51:22 +03:00
|
|
|
struct ip_sf_list *tomb, *sources;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2017-06-12 19:52:26 +03:00
|
|
|
spin_lock_bh(&pmc->lock);
|
|
|
|
tomb = pmc->tomb;
|
|
|
|
pmc->tomb = NULL;
|
|
|
|
sources = pmc->sources;
|
|
|
|
pmc->sources = NULL;
|
|
|
|
pmc->sfmode = MCAST_EXCLUDE;
|
|
|
|
pmc->sfcount[MCAST_INCLUDE] = 0;
|
|
|
|
pmc->sfcount[MCAST_EXCLUDE] = 1;
|
|
|
|
spin_unlock_bh(&pmc->lock);
|
|
|
|
|
2019-05-23 02:51:22 +03:00
|
|
|
ip_sf_list_clear_all(tomb);
|
|
|
|
ip_sf_list_clear_all(sources);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
ipv4, ipv6: kill ip_mc_{join, leave}_group and ipv6_sock_mc_{join, drop}
in favor of their inner __ ones, which doesn't grab rtnl.
As these functions need to operate on a locked socket, we can't be
grabbing rtnl by then. It's too late and doing so causes reversed
locking.
So this patch:
- move rtnl handling to callers instead while already fixing some
reversed locking situations, like on vxlan and ipvs code.
- renames __ ones to not have the __ mark:
__ip_mc_{join,leave}_group -> ip_mc_{join,leave}_group
__ipv6_sock_mc_{join,drop} -> ipv6_sock_mc_{join,drop}
Signed-off-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-03-18 20:50:43 +03:00
|
|
|
/* Join a multicast group
|
|
|
|
*/
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
static int __ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr,
|
|
|
|
unsigned int mode)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2006-09-28 05:30:07 +04:00
|
|
|
__be32 addr = imr->imr_multiaddr.s_addr;
|
2015-02-17 14:19:24 +03:00
|
|
|
struct ip_mc_socklist *iml, *i;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct in_device *in_dev;
|
|
|
|
struct inet_sock *inet = inet_sk(sk);
|
2008-08-14 03:15:57 +04:00
|
|
|
struct net *net = sock_net(sk);
|
2005-07-09 04:38:07 +04:00
|
|
|
int ifindex;
|
2005-04-17 02:20:36 +04:00
|
|
|
int count = 0;
|
2015-02-17 14:19:24 +03:00
|
|
|
int err;
|
|
|
|
|
|
|
|
ASSERT_RTNL();
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2007-12-17 00:45:43 +03:00
|
|
|
if (!ipv4_is_multicast(addr))
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
2008-08-14 03:15:57 +04:00
|
|
|
in_dev = ip_mc_find_dev(net, imr);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (!in_dev) {
|
|
|
|
err = -ENODEV;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = -EADDRINUSE;
|
2005-07-09 04:38:07 +04:00
|
|
|
ifindex = imr->imr_ifindex;
|
2010-11-12 08:46:50 +03:00
|
|
|
for_each_pmc_rtnl(inet, i) {
|
2005-07-09 04:38:07 +04:00
|
|
|
if (i->multi.imr_multiaddr.s_addr == addr &&
|
|
|
|
i->multi.imr_ifindex == ifindex)
|
2005-04-17 02:20:36 +04:00
|
|
|
goto done;
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
err = -ENOBUFS;
|
2022-07-15 20:17:42 +03:00
|
|
|
if (count >= READ_ONCE(net->ipv4.sysctl_igmp_max_memberships))
|
2005-07-09 04:38:07 +04:00
|
|
|
goto done;
|
2008-11-03 11:26:09 +03:00
|
|
|
iml = sock_kmalloc(sk, sizeof(*iml), GFP_KERNEL);
|
2015-04-03 11:17:26 +03:00
|
|
|
if (!iml)
|
2005-04-17 02:20:36 +04:00
|
|
|
goto done;
|
2005-07-09 04:38:07 +04:00
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
memcpy(&iml->multi, imr, sizeof(*imr));
|
2010-11-12 08:46:50 +03:00
|
|
|
iml->next_rcu = inet->mc_list;
|
2005-04-17 02:20:36 +04:00
|
|
|
iml->sflist = NULL;
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
iml->sfmode = mode;
|
2012-01-12 08:41:32 +04:00
|
|
|
rcu_assign_pointer(inet->mc_list, iml);
|
2019-08-20 08:52:47 +03:00
|
|
|
____ip_mc_inc_group(in_dev, addr, mode, GFP_KERNEL);
|
2005-04-17 02:20:36 +04:00
|
|
|
err = 0;
|
|
|
|
done:
|
|
|
|
return err;
|
|
|
|
}
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
|
|
|
|
/* Join ASM (Any-Source Multicast) group
|
|
|
|
*/
|
|
|
|
int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr)
|
|
|
|
{
|
|
|
|
return __ip_mc_join_group(sk, imr, MCAST_EXCLUDE);
|
|
|
|
}
|
2010-07-10 01:22:10 +04:00
|
|
|
EXPORT_SYMBOL(ip_mc_join_group);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
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 a052517a8ff65 ("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: 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>
2018-07-10 17:41:26 +03:00
|
|
|
/* Join SSM (Source-Specific Multicast) group
|
|
|
|
*/
|
|
|
|
int ip_mc_join_group_ssm(struct sock *sk, struct ip_mreqn *imr,
|
|
|
|
unsigned int mode)
|
|
|
|
{
|
|
|
|
return __ip_mc_join_group(sk, imr, mode);
|
|
|
|
}
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
static int ip_mc_leave_src(struct sock *sk, struct ip_mc_socklist *iml,
|
|
|
|
struct in_device *in_dev)
|
|
|
|
{
|
2010-11-12 08:46:50 +03:00
|
|
|
struct ip_sf_socklist *psf = rtnl_dereference(iml->sflist);
|
2005-04-17 02:20:36 +04:00
|
|
|
int err;
|
|
|
|
|
2015-04-03 11:17:26 +03:00
|
|
|
if (!psf) {
|
2005-04-17 02:20:36 +04:00
|
|
|
/* any-source empty exclude case */
|
|
|
|
return ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr,
|
|
|
|
iml->sfmode, 0, NULL, 0);
|
|
|
|
}
|
|
|
|
err = ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr,
|
2010-02-02 18:32:29 +03:00
|
|
|
iml->sfmode, psf->sl_count, psf->sl_addr, 0);
|
2011-08-01 20:19:00 +04:00
|
|
|
RCU_INIT_POINTER(iml->sflist, NULL);
|
2010-02-02 18:32:29 +03:00
|
|
|
/* decrease mem now to avoid the memleak warning */
|
2021-08-05 00:18:50 +03:00
|
|
|
atomic_sub(struct_size(psf, sl_addr, psf->sl_max), &sk->sk_omem_alloc);
|
2011-03-18 06:44:46 +03:00
|
|
|
kfree_rcu(psf, rcu);
|
2005-04-17 02:20:36 +04:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
ipv4, ipv6: kill ip_mc_{join, leave}_group and ipv6_sock_mc_{join, drop}
in favor of their inner __ ones, which doesn't grab rtnl.
As these functions need to operate on a locked socket, we can't be
grabbing rtnl by then. It's too late and doing so causes reversed
locking.
So this patch:
- move rtnl handling to callers instead while already fixing some
reversed locking situations, like on vxlan and ipvs code.
- renames __ ones to not have the __ mark:
__ip_mc_{join,leave}_group -> ip_mc_{join,leave}_group
__ipv6_sock_mc_{join,drop} -> ipv6_sock_mc_{join,drop}
Signed-off-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-03-18 20:50:43 +03:00
|
|
|
int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct inet_sock *inet = inet_sk(sk);
|
2010-11-12 08:46:50 +03:00
|
|
|
struct ip_mc_socklist *iml;
|
|
|
|
struct ip_mc_socklist __rcu **imlp;
|
2005-07-09 04:48:38 +04:00
|
|
|
struct in_device *in_dev;
|
2008-08-14 03:15:57 +04:00
|
|
|
struct net *net = sock_net(sk);
|
2006-09-28 05:30:07 +04:00
|
|
|
__be32 group = imr->imr_multiaddr.s_addr;
|
2005-07-09 04:48:38 +04:00
|
|
|
u32 ifindex;
|
2006-08-18 03:27:39 +04:00
|
|
|
int ret = -EADDRNOTAVAIL;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2015-02-17 14:19:24 +03:00
|
|
|
ASSERT_RTNL();
|
|
|
|
|
2008-08-14 03:15:57 +04:00
|
|
|
in_dev = ip_mc_find_dev(net, imr);
|
2015-12-01 18:31:08 +03:00
|
|
|
if (!imr->imr_ifindex && !imr->imr_address.s_addr && !in_dev) {
|
igmp: fix the problem when mc leave group
The problem was triggered by these steps:
1) create socket, bind and then setsockopt for add mc group.
mreq.imr_multiaddr.s_addr = inet_addr("255.0.0.37");
mreq.imr_interface.s_addr = inet_addr("192.168.1.2");
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
2) drop the mc group for this socket.
mreq.imr_multiaddr.s_addr = inet_addr("255.0.0.37");
mreq.imr_interface.s_addr = inet_addr("0.0.0.0");
setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
3) and then drop the socket, I found the mc group was still used by the dev:
netstat -g
Interface RefCnt Group
--------------- ------ ---------------------
eth2 1 255.0.0.37
Normally even though the IP_DROP_MEMBERSHIP return error, the mc group still need
to be released for the netdev when drop the socket, but this process was broken when
route default is NULL, the reason is that:
The ip_mc_leave_group() will choose the in_dev by the imr_interface.s_addr, if input addr
is NULL, the default route dev will be chosen, then the ifindex is got from the dev,
then polling the inet->mc_list and return -ENODEV, but if the default route dev is NULL,
the in_dev and ifIndex is both NULL, when polling the inet->mc_list, the mc group will be
released from the mc_list, but the dev didn't dec the refcnt for this mc group, so
when dropping the socket, the mc_list is NULL and the dev still keep this group.
v1->v2: According Hideaki's suggestion, we should align with IPv6 (RFC3493) and BSDs,
so I add the checking for the in_dev before polling the mc_list, make sure when
we remove the mc group, dec the refcnt to the real dev which was using the mc address.
The problem would never happened again.
Signed-off-by: Ding Tianhong <dingtianhong@huawei.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2014-07-02 09:50:48 +04:00
|
|
|
ret = -ENODEV;
|
|
|
|
goto out;
|
|
|
|
}
|
2005-07-09 04:48:38 +04:00
|
|
|
ifindex = imr->imr_ifindex;
|
2010-11-12 08:46:50 +03:00
|
|
|
for (imlp = &inet->mc_list;
|
|
|
|
(iml = rtnl_dereference(*imlp)) != NULL;
|
|
|
|
imlp = &iml->next_rcu) {
|
2006-08-18 03:27:39 +04:00
|
|
|
if (iml->multi.imr_multiaddr.s_addr != group)
|
|
|
|
continue;
|
|
|
|
if (ifindex) {
|
|
|
|
if (iml->multi.imr_ifindex != ifindex)
|
|
|
|
continue;
|
|
|
|
} else if (imr->imr_address.s_addr && imr->imr_address.s_addr !=
|
|
|
|
iml->multi.imr_address.s_addr)
|
|
|
|
continue;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2006-08-18 03:27:39 +04:00
|
|
|
(void) ip_mc_leave_src(sk, iml, in_dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
*imlp = iml->next_rcu;
|
2006-08-18 03:27:39 +04:00
|
|
|
|
2015-12-01 18:31:08 +03:00
|
|
|
if (in_dev)
|
|
|
|
ip_mc_dec_group(in_dev, group);
|
2015-02-17 14:19:24 +03:00
|
|
|
|
2010-02-02 18:32:29 +03:00
|
|
|
/* decrease mem now to avoid the memleak warning */
|
|
|
|
atomic_sub(sizeof(*iml), &sk->sk_omem_alloc);
|
2011-03-18 06:45:08 +03:00
|
|
|
kfree_rcu(iml, rcu);
|
2006-08-18 03:27:39 +04:00
|
|
|
return 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
igmp: fix the problem when mc leave group
The problem was triggered by these steps:
1) create socket, bind and then setsockopt for add mc group.
mreq.imr_multiaddr.s_addr = inet_addr("255.0.0.37");
mreq.imr_interface.s_addr = inet_addr("192.168.1.2");
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
2) drop the mc group for this socket.
mreq.imr_multiaddr.s_addr = inet_addr("255.0.0.37");
mreq.imr_interface.s_addr = inet_addr("0.0.0.0");
setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
3) and then drop the socket, I found the mc group was still used by the dev:
netstat -g
Interface RefCnt Group
--------------- ------ ---------------------
eth2 1 255.0.0.37
Normally even though the IP_DROP_MEMBERSHIP return error, the mc group still need
to be released for the netdev when drop the socket, but this process was broken when
route default is NULL, the reason is that:
The ip_mc_leave_group() will choose the in_dev by the imr_interface.s_addr, if input addr
is NULL, the default route dev will be chosen, then the ifindex is got from the dev,
then polling the inet->mc_list and return -ENODEV, but if the default route dev is NULL,
the in_dev and ifIndex is both NULL, when polling the inet->mc_list, the mc group will be
released from the mc_list, but the dev didn't dec the refcnt for this mc group, so
when dropping the socket, the mc_list is NULL and the dev still keep this group.
v1->v2: According Hideaki's suggestion, we should align with IPv6 (RFC3493) and BSDs,
so I add the checking for the in_dev before polling the mc_list, make sure when
we remove the mc group, dec the refcnt to the real dev which was using the mc address.
The problem would never happened again.
Signed-off-by: Ding Tianhong <dingtianhong@huawei.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2014-07-02 09:50:48 +04:00
|
|
|
out:
|
2015-02-17 14:19:24 +03:00
|
|
|
return ret;
|
|
|
|
}
|
2012-10-01 16:32:34 +04:00
|
|
|
EXPORT_SYMBOL(ip_mc_leave_group);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
int ip_mc_source(int add, int omode, struct sock *sk, struct
|
|
|
|
ip_mreq_source *mreqs, int ifindex)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
struct ip_mreqn imr;
|
2006-09-28 05:31:32 +04:00
|
|
|
__be32 addr = mreqs->imr_multiaddr;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct ip_mc_socklist *pmc;
|
|
|
|
struct in_device *in_dev = NULL;
|
|
|
|
struct inet_sock *inet = inet_sk(sk);
|
|
|
|
struct ip_sf_socklist *psl;
|
2008-08-14 03:15:57 +04:00
|
|
|
struct net *net = sock_net(sk);
|
2005-07-09 04:39:23 +04:00
|
|
|
int leavegroup = 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
int i, j, rv;
|
|
|
|
|
2007-12-17 00:45:43 +03:00
|
|
|
if (!ipv4_is_multicast(addr))
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
ipv4, ipv6: kill ip_mc_{join, leave}_group and ipv6_sock_mc_{join, drop}
in favor of their inner __ ones, which doesn't grab rtnl.
As these functions need to operate on a locked socket, we can't be
grabbing rtnl by then. It's too late and doing so causes reversed
locking.
So this patch:
- move rtnl handling to callers instead while already fixing some
reversed locking situations, like on vxlan and ipvs code.
- renames __ ones to not have the __ mark:
__ip_mc_{join,leave}_group -> ip_mc_{join,leave}_group
__ipv6_sock_mc_{join,drop} -> ipv6_sock_mc_{join,drop}
Signed-off-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-03-18 20:50:43 +03:00
|
|
|
ASSERT_RTNL();
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
imr.imr_multiaddr.s_addr = mreqs->imr_multiaddr;
|
|
|
|
imr.imr_address.s_addr = mreqs->imr_interface;
|
|
|
|
imr.imr_ifindex = ifindex;
|
2008-08-14 03:15:57 +04:00
|
|
|
in_dev = ip_mc_find_dev(net, &imr);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (!in_dev) {
|
|
|
|
err = -ENODEV;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
err = -EADDRNOTAVAIL;
|
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
for_each_pmc_rtnl(inet, pmc) {
|
2009-11-30 03:55:45 +03:00
|
|
|
if ((pmc->multi.imr_multiaddr.s_addr ==
|
|
|
|
imr.imr_multiaddr.s_addr) &&
|
|
|
|
(pmc->multi.imr_ifindex == imr.imr_ifindex))
|
2005-04-17 02:20:36 +04:00
|
|
|
break;
|
|
|
|
}
|
2005-07-09 04:45:16 +04:00
|
|
|
if (!pmc) { /* must have a prior join */
|
|
|
|
err = -EINVAL;
|
2005-04-17 02:20:36 +04:00
|
|
|
goto done;
|
2005-07-09 04:45:16 +04:00
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
/* if a source filter was set, must be the same mode as before */
|
|
|
|
if (pmc->sflist) {
|
2005-07-09 04:45:16 +04:00
|
|
|
if (pmc->sfmode != omode) {
|
|
|
|
err = -EINVAL;
|
2005-04-17 02:20:36 +04:00
|
|
|
goto done;
|
2005-07-09 04:45:16 +04:00
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
} else if (pmc->sfmode != omode) {
|
|
|
|
/* allow mode switches for empty-set filters */
|
|
|
|
ip_mc_add_src(in_dev, &mreqs->imr_multiaddr, omode, 0, NULL, 0);
|
2007-02-09 17:24:47 +03:00
|
|
|
ip_mc_del_src(in_dev, &mreqs->imr_multiaddr, pmc->sfmode, 0,
|
2005-04-17 02:20:36 +04:00
|
|
|
NULL, 0);
|
|
|
|
pmc->sfmode = omode;
|
|
|
|
}
|
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
psl = rtnl_dereference(pmc->sflist);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!add) {
|
|
|
|
if (!psl)
|
2005-07-09 04:45:16 +04:00
|
|
|
goto done; /* err = -EADDRNOTAVAIL */
|
2005-04-17 02:20:36 +04:00
|
|
|
rv = !0;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (i = 0; i < psl->sl_count; i++) {
|
2005-04-17 02:20:36 +04:00
|
|
|
rv = memcmp(&psl->sl_addr[i], &mreqs->imr_sourceaddr,
|
2006-09-28 05:31:32 +04:00
|
|
|
sizeof(__be32));
|
2005-04-17 02:20:36 +04:00
|
|
|
if (rv == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (rv) /* source not found */
|
2005-07-09 04:45:16 +04:00
|
|
|
goto done; /* err = -EADDRNOTAVAIL */
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-07-09 04:39:23 +04:00
|
|
|
/* special case - (INCLUDE, empty) == LEAVE_GROUP */
|
|
|
|
if (psl->sl_count == 1 && omode == MCAST_INCLUDE) {
|
|
|
|
leavegroup = 1;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
/* update the interface filter */
|
2007-02-09 17:24:47 +03:00
|
|
|
ip_mc_del_src(in_dev, &mreqs->imr_multiaddr, omode, 1,
|
2005-04-17 02:20:36 +04:00
|
|
|
&mreqs->imr_sourceaddr, 1);
|
|
|
|
|
2013-12-23 10:37:29 +04:00
|
|
|
for (j = i+1; j < psl->sl_count; j++)
|
2005-04-17 02:20:36 +04:00
|
|
|
psl->sl_addr[j-1] = psl->sl_addr[j];
|
|
|
|
psl->sl_count--;
|
|
|
|
err = 0;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
/* else, add a new source to the filter */
|
|
|
|
|
2022-07-15 20:17:43 +03:00
|
|
|
if (psl && psl->sl_count >= READ_ONCE(net->ipv4.sysctl_igmp_max_msf)) {
|
2005-04-17 02:20:36 +04:00
|
|
|
err = -ENOBUFS;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
if (!psl || psl->sl_count == psl->sl_max) {
|
|
|
|
struct ip_sf_socklist *newpsl;
|
|
|
|
int count = IP_SFBLOCK;
|
|
|
|
|
|
|
|
if (psl)
|
|
|
|
count += psl->sl_max;
|
2021-08-05 00:18:50 +03:00
|
|
|
newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr, count),
|
|
|
|
GFP_KERNEL);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!newpsl) {
|
|
|
|
err = -ENOBUFS;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
newpsl->sl_max = count;
|
|
|
|
newpsl->sl_count = count - IP_SFBLOCK;
|
|
|
|
if (psl) {
|
2013-12-23 10:37:29 +04:00
|
|
|
for (i = 0; i < psl->sl_count; i++)
|
2005-04-17 02:20:36 +04:00
|
|
|
newpsl->sl_addr[i] = psl->sl_addr[i];
|
2010-02-02 18:32:29 +03:00
|
|
|
/* decrease mem now to avoid the memleak warning */
|
2021-08-05 00:18:50 +03:00
|
|
|
atomic_sub(struct_size(psl, sl_addr, psl->sl_max),
|
|
|
|
&sk->sk_omem_alloc);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
2012-01-12 08:41:32 +04:00
|
|
|
rcu_assign_pointer(pmc->sflist, newpsl);
|
net: igmp: respect RCU rules in ip_mc_source() and ip_mc_msfilter()
commit dba5bdd57bea587ea4f0b79b03c71135f84a7e8b upstream.
syzbot reported an UAF in ip_mc_sf_allow() [1]
Whenever RCU protected list replaces an object,
the pointer to the new object needs to be updated
_before_ the call to kfree_rcu() or call_rcu()
Because kfree_rcu(ptr, rcu) got support for NULL ptr
only recently in commit 12edff045bc6 ("rcu: Make kfree_rcu()
ignore NULL pointers"), I chose to use the conditional
to make sure stable backports won't miss this detail.
if (psl)
kfree_rcu(psl, rcu);
net/ipv6/mcast.c has similar issues, addressed in a separate patch.
[1]
BUG: KASAN: use-after-free in ip_mc_sf_allow+0x6bb/0x6d0 net/ipv4/igmp.c:2655
Read of size 4 at addr ffff88807d37b904 by task syz-executor.5/908
CPU: 0 PID: 908 Comm: syz-executor.5 Not tainted 5.18.0-rc4-syzkaller-00064-g8f4dd16603ce #0
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:88 [inline]
dump_stack_lvl+0xcd/0x134 lib/dump_stack.c:106
print_address_description.constprop.0.cold+0xeb/0x467 mm/kasan/report.c:313
print_report mm/kasan/report.c:429 [inline]
kasan_report.cold+0xf4/0x1c6 mm/kasan/report.c:491
ip_mc_sf_allow+0x6bb/0x6d0 net/ipv4/igmp.c:2655
raw_v4_input net/ipv4/raw.c:190 [inline]
raw_local_deliver+0x4d1/0xbe0 net/ipv4/raw.c:218
ip_protocol_deliver_rcu+0xcf/0xb30 net/ipv4/ip_input.c:193
ip_local_deliver_finish+0x2ee/0x4c0 net/ipv4/ip_input.c:233
NF_HOOK include/linux/netfilter.h:307 [inline]
NF_HOOK include/linux/netfilter.h:301 [inline]
ip_local_deliver+0x1b3/0x200 net/ipv4/ip_input.c:254
dst_input include/net/dst.h:461 [inline]
ip_rcv_finish+0x1cb/0x2f0 net/ipv4/ip_input.c:437
NF_HOOK include/linux/netfilter.h:307 [inline]
NF_HOOK include/linux/netfilter.h:301 [inline]
ip_rcv+0xaa/0xd0 net/ipv4/ip_input.c:556
__netif_receive_skb_one_core+0x114/0x180 net/core/dev.c:5405
__netif_receive_skb+0x24/0x1b0 net/core/dev.c:5519
netif_receive_skb_internal net/core/dev.c:5605 [inline]
netif_receive_skb+0x13e/0x8e0 net/core/dev.c:5664
tun_rx_batched.isra.0+0x460/0x720 drivers/net/tun.c:1534
tun_get_user+0x28b7/0x3e30 drivers/net/tun.c:1985
tun_chr_write_iter+0xdb/0x200 drivers/net/tun.c:2015
call_write_iter include/linux/fs.h:2050 [inline]
new_sync_write+0x38a/0x560 fs/read_write.c:504
vfs_write+0x7c0/0xac0 fs/read_write.c:591
ksys_write+0x127/0x250 fs/read_write.c:644
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
RIP: 0033:0x7f3f12c3bbff
Code: 89 54 24 18 48 89 74 24 10 89 7c 24 08 e8 99 fd ff ff 48 8b 54 24 18 48 8b 74 24 10 41 89 c0 8b 7c 24 08 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 31 44 89 c7 48 89 44 24 08 e8 cc fd ff ff 48
RSP: 002b:00007f3f13ea9130 EFLAGS: 00000293 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007f3f12d9bf60 RCX: 00007f3f12c3bbff
RDX: 0000000000000036 RSI: 0000000020002ac0 RDI: 00000000000000c8
RBP: 00007f3f12ce308d R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000036 R11: 0000000000000293 R12: 0000000000000000
R13: 00007fffb68dd79f R14: 00007f3f13ea9300 R15: 0000000000022000
</TASK>
Allocated by task 908:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
kasan_set_track mm/kasan/common.c:45 [inline]
set_alloc_info mm/kasan/common.c:436 [inline]
____kasan_kmalloc mm/kasan/common.c:515 [inline]
____kasan_kmalloc mm/kasan/common.c:474 [inline]
__kasan_kmalloc+0xa6/0xd0 mm/kasan/common.c:524
kasan_kmalloc include/linux/kasan.h:234 [inline]
__do_kmalloc mm/slab.c:3710 [inline]
__kmalloc+0x209/0x4d0 mm/slab.c:3719
kmalloc include/linux/slab.h:586 [inline]
sock_kmalloc net/core/sock.c:2501 [inline]
sock_kmalloc+0xb5/0x100 net/core/sock.c:2492
ip_mc_source+0xba2/0x1100 net/ipv4/igmp.c:2392
do_ip_setsockopt net/ipv4/ip_sockglue.c:1296 [inline]
ip_setsockopt+0x2312/0x3ab0 net/ipv4/ip_sockglue.c:1432
raw_setsockopt+0x274/0x2c0 net/ipv4/raw.c:861
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Freed by task 753:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
kasan_set_track+0x21/0x30 mm/kasan/common.c:45
kasan_set_free_info+0x20/0x30 mm/kasan/generic.c:370
____kasan_slab_free mm/kasan/common.c:366 [inline]
____kasan_slab_free+0x13d/0x180 mm/kasan/common.c:328
kasan_slab_free include/linux/kasan.h:200 [inline]
__cache_free mm/slab.c:3439 [inline]
kmem_cache_free_bulk+0x69/0x460 mm/slab.c:3774
kfree_bulk include/linux/slab.h:437 [inline]
kfree_rcu_work+0x51c/0xa10 kernel/rcu/tree.c:3318
process_one_work+0x996/0x1610 kernel/workqueue.c:2289
worker_thread+0x665/0x1080 kernel/workqueue.c:2436
kthread+0x2e9/0x3a0 kernel/kthread.c:376
ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:298
Last potentially related work creation:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
__kasan_record_aux_stack+0x7e/0x90 mm/kasan/generic.c:348
kvfree_call_rcu+0x74/0x990 kernel/rcu/tree.c:3595
ip_mc_msfilter+0x712/0xb60 net/ipv4/igmp.c:2510
do_ip_setsockopt net/ipv4/ip_sockglue.c:1257 [inline]
ip_setsockopt+0x32e1/0x3ab0 net/ipv4/ip_sockglue.c:1432
raw_setsockopt+0x274/0x2c0 net/ipv4/raw.c:861
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Second to last potentially related work creation:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
__kasan_record_aux_stack+0x7e/0x90 mm/kasan/generic.c:348
call_rcu+0x99/0x790 kernel/rcu/tree.c:3074
mpls_dev_notify+0x552/0x8a0 net/mpls/af_mpls.c:1656
notifier_call_chain+0xb5/0x200 kernel/notifier.c:84
call_netdevice_notifiers_info+0xb5/0x130 net/core/dev.c:1938
call_netdevice_notifiers_extack net/core/dev.c:1976 [inline]
call_netdevice_notifiers net/core/dev.c:1990 [inline]
unregister_netdevice_many+0x92e/0x1890 net/core/dev.c:10751
default_device_exit_batch+0x449/0x590 net/core/dev.c:11245
ops_exit_list+0x125/0x170 net/core/net_namespace.c:167
cleanup_net+0x4ea/0xb00 net/core/net_namespace.c:594
process_one_work+0x996/0x1610 kernel/workqueue.c:2289
worker_thread+0x665/0x1080 kernel/workqueue.c:2436
kthread+0x2e9/0x3a0 kernel/kthread.c:376
ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:298
The buggy address belongs to the object at ffff88807d37b900
which belongs to the cache kmalloc-64 of size 64
The buggy address is located 4 bytes inside of
64-byte region [ffff88807d37b900, ffff88807d37b940)
The buggy address belongs to the physical page:
page:ffffea0001f4dec0 refcount:1 mapcount:0 mapping:0000000000000000 index:0xffff88807d37b180 pfn:0x7d37b
flags: 0xfff00000000200(slab|node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000200 ffff888010c41340 ffffea0001c795c8 ffff888010c40200
raw: ffff88807d37b180 ffff88807d37b000 000000010000001f 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x342040(__GFP_IO|__GFP_NOWARN|__GFP_COMP|__GFP_HARDWALL|__GFP_THISNODE), pid 2963, tgid 2963 (udevd), ts 139732238007, free_ts 139730893262
prep_new_page mm/page_alloc.c:2441 [inline]
get_page_from_freelist+0xba2/0x3e00 mm/page_alloc.c:4182
__alloc_pages+0x1b2/0x500 mm/page_alloc.c:5408
__alloc_pages_node include/linux/gfp.h:587 [inline]
kmem_getpages mm/slab.c:1378 [inline]
cache_grow_begin+0x75/0x350 mm/slab.c:2584
cache_alloc_refill+0x27f/0x380 mm/slab.c:2957
____cache_alloc mm/slab.c:3040 [inline]
____cache_alloc mm/slab.c:3023 [inline]
__do_cache_alloc mm/slab.c:3267 [inline]
slab_alloc mm/slab.c:3309 [inline]
__do_kmalloc mm/slab.c:3708 [inline]
__kmalloc+0x3b3/0x4d0 mm/slab.c:3719
kmalloc include/linux/slab.h:586 [inline]
kzalloc include/linux/slab.h:714 [inline]
tomoyo_encode2.part.0+0xe9/0x3a0 security/tomoyo/realpath.c:45
tomoyo_encode2 security/tomoyo/realpath.c:31 [inline]
tomoyo_encode+0x28/0x50 security/tomoyo/realpath.c:80
tomoyo_realpath_from_path+0x186/0x620 security/tomoyo/realpath.c:288
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_path_perm+0x21b/0x400 security/tomoyo/file.c:822
security_inode_getattr+0xcf/0x140 security/security.c:1350
vfs_getattr fs/stat.c:157 [inline]
vfs_statx+0x16a/0x390 fs/stat.c:232
vfs_fstatat+0x8c/0xb0 fs/stat.c:255
__do_sys_newfstatat+0x91/0x110 fs/stat.c:425
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
page last free stack trace:
reset_page_owner include/linux/page_owner.h:24 [inline]
free_pages_prepare mm/page_alloc.c:1356 [inline]
free_pcp_prepare+0x549/0xd20 mm/page_alloc.c:1406
free_unref_page_prepare mm/page_alloc.c:3328 [inline]
free_unref_page+0x19/0x6a0 mm/page_alloc.c:3423
__vunmap+0x85d/0xd30 mm/vmalloc.c:2667
__vfree+0x3c/0xd0 mm/vmalloc.c:2715
vfree+0x5a/0x90 mm/vmalloc.c:2746
__do_replace+0x16b/0x890 net/ipv6/netfilter/ip6_tables.c:1117
do_replace net/ipv6/netfilter/ip6_tables.c:1157 [inline]
do_ip6t_set_ctl+0x90d/0xb90 net/ipv6/netfilter/ip6_tables.c:1639
nf_setsockopt+0x83/0xe0 net/netfilter/nf_sockopt.c:101
ipv6_setsockopt+0x122/0x180 net/ipv6/ipv6_sockglue.c:1026
tcp_setsockopt+0x136/0x2520 net/ipv4/tcp.c:3696
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Memory state around the buggy address:
ffff88807d37b800: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
ffff88807d37b880: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
>ffff88807d37b900: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
^
ffff88807d37b980: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff88807d37ba00: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
Fixes: c85bb41e9318 ("igmp: fix ip_mc_sf_allow race [v5]")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: syzbot <syzkaller@googlegroups.com>
Cc: Flavio Leitner <fbl@sysclose.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-04-29 18:42:57 +03:00
|
|
|
if (psl)
|
|
|
|
kfree_rcu(psl, rcu);
|
2010-02-02 18:32:29 +03:00
|
|
|
psl = newpsl;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
rv = 1; /* > 0 for insert logic below if sl_count is 0 */
|
2013-12-23 10:37:29 +04:00
|
|
|
for (i = 0; i < psl->sl_count; i++) {
|
2005-04-17 02:20:36 +04:00
|
|
|
rv = memcmp(&psl->sl_addr[i], &mreqs->imr_sourceaddr,
|
2006-09-28 05:31:32 +04:00
|
|
|
sizeof(__be32));
|
2005-04-17 02:20:36 +04:00
|
|
|
if (rv == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (rv == 0) /* address already there is an error */
|
|
|
|
goto done;
|
2013-12-23 10:37:29 +04:00
|
|
|
for (j = psl->sl_count-1; j >= i; j--)
|
2005-04-17 02:20:36 +04:00
|
|
|
psl->sl_addr[j+1] = psl->sl_addr[j];
|
|
|
|
psl->sl_addr[i] = mreqs->imr_sourceaddr;
|
|
|
|
psl->sl_count++;
|
|
|
|
err = 0;
|
|
|
|
/* update the interface list */
|
2007-02-09 17:24:47 +03:00
|
|
|
ip_mc_add_src(in_dev, &mreqs->imr_multiaddr, omode, 1,
|
2005-04-17 02:20:36 +04:00
|
|
|
&mreqs->imr_sourceaddr, 1);
|
|
|
|
done:
|
2005-07-09 04:39:23 +04:00
|
|
|
if (leavegroup)
|
ipv4, ipv6: kill ip_mc_{join, leave}_group and ipv6_sock_mc_{join, drop}
in favor of their inner __ ones, which doesn't grab rtnl.
As these functions need to operate on a locked socket, we can't be
grabbing rtnl by then. It's too late and doing so causes reversed
locking.
So this patch:
- move rtnl handling to callers instead while already fixing some
reversed locking situations, like on vxlan and ipvs code.
- renames __ ones to not have the __ mark:
__ip_mc_{join,leave}_group -> ip_mc_{join,leave}_group
__ipv6_sock_mc_{join,drop} -> ipv6_sock_mc_{join,drop}
Signed-off-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-03-18 20:50:43 +03:00
|
|
|
err = ip_mc_leave_group(sk, &imr);
|
2005-04-17 02:20:36 +04:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ip_mc_msfilter(struct sock *sk, struct ip_msfilter *msf, int ifindex)
|
|
|
|
{
|
2005-07-09 04:47:28 +04:00
|
|
|
int err = 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct ip_mreqn imr;
|
2006-09-28 05:31:32 +04:00
|
|
|
__be32 addr = msf->imsf_multiaddr;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct ip_mc_socklist *pmc;
|
|
|
|
struct in_device *in_dev;
|
|
|
|
struct inet_sock *inet = inet_sk(sk);
|
|
|
|
struct ip_sf_socklist *newpsl, *psl;
|
2008-08-14 03:15:57 +04:00
|
|
|
struct net *net = sock_net(sk);
|
2005-07-09 04:47:28 +04:00
|
|
|
int leavegroup = 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2007-12-17 00:45:43 +03:00
|
|
|
if (!ipv4_is_multicast(addr))
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EINVAL;
|
|
|
|
if (msf->imsf_fmode != MCAST_INCLUDE &&
|
|
|
|
msf->imsf_fmode != MCAST_EXCLUDE)
|
|
|
|
return -EINVAL;
|
|
|
|
|
ipv4, ipv6: kill ip_mc_{join, leave}_group and ipv6_sock_mc_{join, drop}
in favor of their inner __ ones, which doesn't grab rtnl.
As these functions need to operate on a locked socket, we can't be
grabbing rtnl by then. It's too late and doing so causes reversed
locking.
So this patch:
- move rtnl handling to callers instead while already fixing some
reversed locking situations, like on vxlan and ipvs code.
- renames __ ones to not have the __ mark:
__ip_mc_{join,leave}_group -> ip_mc_{join,leave}_group
__ipv6_sock_mc_{join,drop} -> ipv6_sock_mc_{join,drop}
Signed-off-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-03-18 20:50:43 +03:00
|
|
|
ASSERT_RTNL();
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
imr.imr_multiaddr.s_addr = msf->imsf_multiaddr;
|
|
|
|
imr.imr_address.s_addr = msf->imsf_interface;
|
|
|
|
imr.imr_ifindex = ifindex;
|
2008-08-14 03:15:57 +04:00
|
|
|
in_dev = ip_mc_find_dev(net, &imr);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (!in_dev) {
|
|
|
|
err = -ENODEV;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
2005-07-09 04:47:28 +04:00
|
|
|
/* special case - (INCLUDE, empty) == LEAVE_GROUP */
|
|
|
|
if (msf->imsf_fmode == MCAST_INCLUDE && msf->imsf_numsrc == 0) {
|
|
|
|
leavegroup = 1;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
for_each_pmc_rtnl(inet, pmc) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc->multi.imr_multiaddr.s_addr == msf->imsf_multiaddr &&
|
|
|
|
pmc->multi.imr_ifindex == imr.imr_ifindex)
|
|
|
|
break;
|
|
|
|
}
|
2005-07-09 04:45:16 +04:00
|
|
|
if (!pmc) { /* must have a prior join */
|
|
|
|
err = -EINVAL;
|
2005-04-17 02:20:36 +04:00
|
|
|
goto done;
|
2005-07-09 04:45:16 +04:00
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
if (msf->imsf_numsrc) {
|
2021-08-05 00:18:50 +03:00
|
|
|
newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr,
|
|
|
|
msf->imsf_numsrc),
|
|
|
|
GFP_KERNEL);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!newpsl) {
|
|
|
|
err = -ENOBUFS;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
newpsl->sl_max = newpsl->sl_count = msf->imsf_numsrc;
|
2021-07-31 20:08:30 +03:00
|
|
|
memcpy(newpsl->sl_addr, msf->imsf_slist_flex,
|
|
|
|
flex_array_size(msf, imsf_slist_flex, msf->imsf_numsrc));
|
2005-04-17 02:20:36 +04:00
|
|
|
err = ip_mc_add_src(in_dev, &msf->imsf_multiaddr,
|
|
|
|
msf->imsf_fmode, newpsl->sl_count, newpsl->sl_addr, 0);
|
|
|
|
if (err) {
|
2021-08-05 00:18:50 +03:00
|
|
|
sock_kfree_s(sk, newpsl,
|
|
|
|
struct_size(newpsl, sl_addr,
|
|
|
|
newpsl->sl_max));
|
2005-04-17 02:20:36 +04:00
|
|
|
goto done;
|
|
|
|
}
|
2005-10-28 04:02:08 +04:00
|
|
|
} else {
|
2005-04-17 02:20:36 +04:00
|
|
|
newpsl = NULL;
|
2005-10-28 04:02:08 +04:00
|
|
|
(void) ip_mc_add_src(in_dev, &msf->imsf_multiaddr,
|
|
|
|
msf->imsf_fmode, 0, NULL, 0);
|
|
|
|
}
|
2010-11-12 08:46:50 +03:00
|
|
|
psl = rtnl_dereference(pmc->sflist);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (psl) {
|
|
|
|
(void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode,
|
|
|
|
psl->sl_count, psl->sl_addr, 0);
|
2010-02-02 18:32:29 +03:00
|
|
|
/* decrease mem now to avoid the memleak warning */
|
2021-08-05 00:18:50 +03:00
|
|
|
atomic_sub(struct_size(psl, sl_addr, psl->sl_max),
|
|
|
|
&sk->sk_omem_alloc);
|
net: igmp: respect RCU rules in ip_mc_source() and ip_mc_msfilter()
commit dba5bdd57bea587ea4f0b79b03c71135f84a7e8b upstream.
syzbot reported an UAF in ip_mc_sf_allow() [1]
Whenever RCU protected list replaces an object,
the pointer to the new object needs to be updated
_before_ the call to kfree_rcu() or call_rcu()
Because kfree_rcu(ptr, rcu) got support for NULL ptr
only recently in commit 12edff045bc6 ("rcu: Make kfree_rcu()
ignore NULL pointers"), I chose to use the conditional
to make sure stable backports won't miss this detail.
if (psl)
kfree_rcu(psl, rcu);
net/ipv6/mcast.c has similar issues, addressed in a separate patch.
[1]
BUG: KASAN: use-after-free in ip_mc_sf_allow+0x6bb/0x6d0 net/ipv4/igmp.c:2655
Read of size 4 at addr ffff88807d37b904 by task syz-executor.5/908
CPU: 0 PID: 908 Comm: syz-executor.5 Not tainted 5.18.0-rc4-syzkaller-00064-g8f4dd16603ce #0
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:88 [inline]
dump_stack_lvl+0xcd/0x134 lib/dump_stack.c:106
print_address_description.constprop.0.cold+0xeb/0x467 mm/kasan/report.c:313
print_report mm/kasan/report.c:429 [inline]
kasan_report.cold+0xf4/0x1c6 mm/kasan/report.c:491
ip_mc_sf_allow+0x6bb/0x6d0 net/ipv4/igmp.c:2655
raw_v4_input net/ipv4/raw.c:190 [inline]
raw_local_deliver+0x4d1/0xbe0 net/ipv4/raw.c:218
ip_protocol_deliver_rcu+0xcf/0xb30 net/ipv4/ip_input.c:193
ip_local_deliver_finish+0x2ee/0x4c0 net/ipv4/ip_input.c:233
NF_HOOK include/linux/netfilter.h:307 [inline]
NF_HOOK include/linux/netfilter.h:301 [inline]
ip_local_deliver+0x1b3/0x200 net/ipv4/ip_input.c:254
dst_input include/net/dst.h:461 [inline]
ip_rcv_finish+0x1cb/0x2f0 net/ipv4/ip_input.c:437
NF_HOOK include/linux/netfilter.h:307 [inline]
NF_HOOK include/linux/netfilter.h:301 [inline]
ip_rcv+0xaa/0xd0 net/ipv4/ip_input.c:556
__netif_receive_skb_one_core+0x114/0x180 net/core/dev.c:5405
__netif_receive_skb+0x24/0x1b0 net/core/dev.c:5519
netif_receive_skb_internal net/core/dev.c:5605 [inline]
netif_receive_skb+0x13e/0x8e0 net/core/dev.c:5664
tun_rx_batched.isra.0+0x460/0x720 drivers/net/tun.c:1534
tun_get_user+0x28b7/0x3e30 drivers/net/tun.c:1985
tun_chr_write_iter+0xdb/0x200 drivers/net/tun.c:2015
call_write_iter include/linux/fs.h:2050 [inline]
new_sync_write+0x38a/0x560 fs/read_write.c:504
vfs_write+0x7c0/0xac0 fs/read_write.c:591
ksys_write+0x127/0x250 fs/read_write.c:644
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
RIP: 0033:0x7f3f12c3bbff
Code: 89 54 24 18 48 89 74 24 10 89 7c 24 08 e8 99 fd ff ff 48 8b 54 24 18 48 8b 74 24 10 41 89 c0 8b 7c 24 08 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 31 44 89 c7 48 89 44 24 08 e8 cc fd ff ff 48
RSP: 002b:00007f3f13ea9130 EFLAGS: 00000293 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007f3f12d9bf60 RCX: 00007f3f12c3bbff
RDX: 0000000000000036 RSI: 0000000020002ac0 RDI: 00000000000000c8
RBP: 00007f3f12ce308d R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000036 R11: 0000000000000293 R12: 0000000000000000
R13: 00007fffb68dd79f R14: 00007f3f13ea9300 R15: 0000000000022000
</TASK>
Allocated by task 908:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
kasan_set_track mm/kasan/common.c:45 [inline]
set_alloc_info mm/kasan/common.c:436 [inline]
____kasan_kmalloc mm/kasan/common.c:515 [inline]
____kasan_kmalloc mm/kasan/common.c:474 [inline]
__kasan_kmalloc+0xa6/0xd0 mm/kasan/common.c:524
kasan_kmalloc include/linux/kasan.h:234 [inline]
__do_kmalloc mm/slab.c:3710 [inline]
__kmalloc+0x209/0x4d0 mm/slab.c:3719
kmalloc include/linux/slab.h:586 [inline]
sock_kmalloc net/core/sock.c:2501 [inline]
sock_kmalloc+0xb5/0x100 net/core/sock.c:2492
ip_mc_source+0xba2/0x1100 net/ipv4/igmp.c:2392
do_ip_setsockopt net/ipv4/ip_sockglue.c:1296 [inline]
ip_setsockopt+0x2312/0x3ab0 net/ipv4/ip_sockglue.c:1432
raw_setsockopt+0x274/0x2c0 net/ipv4/raw.c:861
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Freed by task 753:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
kasan_set_track+0x21/0x30 mm/kasan/common.c:45
kasan_set_free_info+0x20/0x30 mm/kasan/generic.c:370
____kasan_slab_free mm/kasan/common.c:366 [inline]
____kasan_slab_free+0x13d/0x180 mm/kasan/common.c:328
kasan_slab_free include/linux/kasan.h:200 [inline]
__cache_free mm/slab.c:3439 [inline]
kmem_cache_free_bulk+0x69/0x460 mm/slab.c:3774
kfree_bulk include/linux/slab.h:437 [inline]
kfree_rcu_work+0x51c/0xa10 kernel/rcu/tree.c:3318
process_one_work+0x996/0x1610 kernel/workqueue.c:2289
worker_thread+0x665/0x1080 kernel/workqueue.c:2436
kthread+0x2e9/0x3a0 kernel/kthread.c:376
ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:298
Last potentially related work creation:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
__kasan_record_aux_stack+0x7e/0x90 mm/kasan/generic.c:348
kvfree_call_rcu+0x74/0x990 kernel/rcu/tree.c:3595
ip_mc_msfilter+0x712/0xb60 net/ipv4/igmp.c:2510
do_ip_setsockopt net/ipv4/ip_sockglue.c:1257 [inline]
ip_setsockopt+0x32e1/0x3ab0 net/ipv4/ip_sockglue.c:1432
raw_setsockopt+0x274/0x2c0 net/ipv4/raw.c:861
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Second to last potentially related work creation:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
__kasan_record_aux_stack+0x7e/0x90 mm/kasan/generic.c:348
call_rcu+0x99/0x790 kernel/rcu/tree.c:3074
mpls_dev_notify+0x552/0x8a0 net/mpls/af_mpls.c:1656
notifier_call_chain+0xb5/0x200 kernel/notifier.c:84
call_netdevice_notifiers_info+0xb5/0x130 net/core/dev.c:1938
call_netdevice_notifiers_extack net/core/dev.c:1976 [inline]
call_netdevice_notifiers net/core/dev.c:1990 [inline]
unregister_netdevice_many+0x92e/0x1890 net/core/dev.c:10751
default_device_exit_batch+0x449/0x590 net/core/dev.c:11245
ops_exit_list+0x125/0x170 net/core/net_namespace.c:167
cleanup_net+0x4ea/0xb00 net/core/net_namespace.c:594
process_one_work+0x996/0x1610 kernel/workqueue.c:2289
worker_thread+0x665/0x1080 kernel/workqueue.c:2436
kthread+0x2e9/0x3a0 kernel/kthread.c:376
ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:298
The buggy address belongs to the object at ffff88807d37b900
which belongs to the cache kmalloc-64 of size 64
The buggy address is located 4 bytes inside of
64-byte region [ffff88807d37b900, ffff88807d37b940)
The buggy address belongs to the physical page:
page:ffffea0001f4dec0 refcount:1 mapcount:0 mapping:0000000000000000 index:0xffff88807d37b180 pfn:0x7d37b
flags: 0xfff00000000200(slab|node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000200 ffff888010c41340 ffffea0001c795c8 ffff888010c40200
raw: ffff88807d37b180 ffff88807d37b000 000000010000001f 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x342040(__GFP_IO|__GFP_NOWARN|__GFP_COMP|__GFP_HARDWALL|__GFP_THISNODE), pid 2963, tgid 2963 (udevd), ts 139732238007, free_ts 139730893262
prep_new_page mm/page_alloc.c:2441 [inline]
get_page_from_freelist+0xba2/0x3e00 mm/page_alloc.c:4182
__alloc_pages+0x1b2/0x500 mm/page_alloc.c:5408
__alloc_pages_node include/linux/gfp.h:587 [inline]
kmem_getpages mm/slab.c:1378 [inline]
cache_grow_begin+0x75/0x350 mm/slab.c:2584
cache_alloc_refill+0x27f/0x380 mm/slab.c:2957
____cache_alloc mm/slab.c:3040 [inline]
____cache_alloc mm/slab.c:3023 [inline]
__do_cache_alloc mm/slab.c:3267 [inline]
slab_alloc mm/slab.c:3309 [inline]
__do_kmalloc mm/slab.c:3708 [inline]
__kmalloc+0x3b3/0x4d0 mm/slab.c:3719
kmalloc include/linux/slab.h:586 [inline]
kzalloc include/linux/slab.h:714 [inline]
tomoyo_encode2.part.0+0xe9/0x3a0 security/tomoyo/realpath.c:45
tomoyo_encode2 security/tomoyo/realpath.c:31 [inline]
tomoyo_encode+0x28/0x50 security/tomoyo/realpath.c:80
tomoyo_realpath_from_path+0x186/0x620 security/tomoyo/realpath.c:288
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_path_perm+0x21b/0x400 security/tomoyo/file.c:822
security_inode_getattr+0xcf/0x140 security/security.c:1350
vfs_getattr fs/stat.c:157 [inline]
vfs_statx+0x16a/0x390 fs/stat.c:232
vfs_fstatat+0x8c/0xb0 fs/stat.c:255
__do_sys_newfstatat+0x91/0x110 fs/stat.c:425
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
page last free stack trace:
reset_page_owner include/linux/page_owner.h:24 [inline]
free_pages_prepare mm/page_alloc.c:1356 [inline]
free_pcp_prepare+0x549/0xd20 mm/page_alloc.c:1406
free_unref_page_prepare mm/page_alloc.c:3328 [inline]
free_unref_page+0x19/0x6a0 mm/page_alloc.c:3423
__vunmap+0x85d/0xd30 mm/vmalloc.c:2667
__vfree+0x3c/0xd0 mm/vmalloc.c:2715
vfree+0x5a/0x90 mm/vmalloc.c:2746
__do_replace+0x16b/0x890 net/ipv6/netfilter/ip6_tables.c:1117
do_replace net/ipv6/netfilter/ip6_tables.c:1157 [inline]
do_ip6t_set_ctl+0x90d/0xb90 net/ipv6/netfilter/ip6_tables.c:1639
nf_setsockopt+0x83/0xe0 net/netfilter/nf_sockopt.c:101
ipv6_setsockopt+0x122/0x180 net/ipv6/ipv6_sockglue.c:1026
tcp_setsockopt+0x136/0x2520 net/ipv4/tcp.c:3696
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Memory state around the buggy address:
ffff88807d37b800: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
ffff88807d37b880: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
>ffff88807d37b900: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
^
ffff88807d37b980: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff88807d37ba00: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
Fixes: c85bb41e9318 ("igmp: fix ip_mc_sf_allow race [v5]")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: syzbot <syzkaller@googlegroups.com>
Cc: Flavio Leitner <fbl@sysclose.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-04-29 18:42:57 +03:00
|
|
|
} else {
|
2005-04-17 02:20:36 +04:00
|
|
|
(void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode,
|
|
|
|
0, NULL, 0);
|
net: igmp: respect RCU rules in ip_mc_source() and ip_mc_msfilter()
commit dba5bdd57bea587ea4f0b79b03c71135f84a7e8b upstream.
syzbot reported an UAF in ip_mc_sf_allow() [1]
Whenever RCU protected list replaces an object,
the pointer to the new object needs to be updated
_before_ the call to kfree_rcu() or call_rcu()
Because kfree_rcu(ptr, rcu) got support for NULL ptr
only recently in commit 12edff045bc6 ("rcu: Make kfree_rcu()
ignore NULL pointers"), I chose to use the conditional
to make sure stable backports won't miss this detail.
if (psl)
kfree_rcu(psl, rcu);
net/ipv6/mcast.c has similar issues, addressed in a separate patch.
[1]
BUG: KASAN: use-after-free in ip_mc_sf_allow+0x6bb/0x6d0 net/ipv4/igmp.c:2655
Read of size 4 at addr ffff88807d37b904 by task syz-executor.5/908
CPU: 0 PID: 908 Comm: syz-executor.5 Not tainted 5.18.0-rc4-syzkaller-00064-g8f4dd16603ce #0
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:88 [inline]
dump_stack_lvl+0xcd/0x134 lib/dump_stack.c:106
print_address_description.constprop.0.cold+0xeb/0x467 mm/kasan/report.c:313
print_report mm/kasan/report.c:429 [inline]
kasan_report.cold+0xf4/0x1c6 mm/kasan/report.c:491
ip_mc_sf_allow+0x6bb/0x6d0 net/ipv4/igmp.c:2655
raw_v4_input net/ipv4/raw.c:190 [inline]
raw_local_deliver+0x4d1/0xbe0 net/ipv4/raw.c:218
ip_protocol_deliver_rcu+0xcf/0xb30 net/ipv4/ip_input.c:193
ip_local_deliver_finish+0x2ee/0x4c0 net/ipv4/ip_input.c:233
NF_HOOK include/linux/netfilter.h:307 [inline]
NF_HOOK include/linux/netfilter.h:301 [inline]
ip_local_deliver+0x1b3/0x200 net/ipv4/ip_input.c:254
dst_input include/net/dst.h:461 [inline]
ip_rcv_finish+0x1cb/0x2f0 net/ipv4/ip_input.c:437
NF_HOOK include/linux/netfilter.h:307 [inline]
NF_HOOK include/linux/netfilter.h:301 [inline]
ip_rcv+0xaa/0xd0 net/ipv4/ip_input.c:556
__netif_receive_skb_one_core+0x114/0x180 net/core/dev.c:5405
__netif_receive_skb+0x24/0x1b0 net/core/dev.c:5519
netif_receive_skb_internal net/core/dev.c:5605 [inline]
netif_receive_skb+0x13e/0x8e0 net/core/dev.c:5664
tun_rx_batched.isra.0+0x460/0x720 drivers/net/tun.c:1534
tun_get_user+0x28b7/0x3e30 drivers/net/tun.c:1985
tun_chr_write_iter+0xdb/0x200 drivers/net/tun.c:2015
call_write_iter include/linux/fs.h:2050 [inline]
new_sync_write+0x38a/0x560 fs/read_write.c:504
vfs_write+0x7c0/0xac0 fs/read_write.c:591
ksys_write+0x127/0x250 fs/read_write.c:644
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
RIP: 0033:0x7f3f12c3bbff
Code: 89 54 24 18 48 89 74 24 10 89 7c 24 08 e8 99 fd ff ff 48 8b 54 24 18 48 8b 74 24 10 41 89 c0 8b 7c 24 08 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 31 44 89 c7 48 89 44 24 08 e8 cc fd ff ff 48
RSP: 002b:00007f3f13ea9130 EFLAGS: 00000293 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007f3f12d9bf60 RCX: 00007f3f12c3bbff
RDX: 0000000000000036 RSI: 0000000020002ac0 RDI: 00000000000000c8
RBP: 00007f3f12ce308d R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000036 R11: 0000000000000293 R12: 0000000000000000
R13: 00007fffb68dd79f R14: 00007f3f13ea9300 R15: 0000000000022000
</TASK>
Allocated by task 908:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
kasan_set_track mm/kasan/common.c:45 [inline]
set_alloc_info mm/kasan/common.c:436 [inline]
____kasan_kmalloc mm/kasan/common.c:515 [inline]
____kasan_kmalloc mm/kasan/common.c:474 [inline]
__kasan_kmalloc+0xa6/0xd0 mm/kasan/common.c:524
kasan_kmalloc include/linux/kasan.h:234 [inline]
__do_kmalloc mm/slab.c:3710 [inline]
__kmalloc+0x209/0x4d0 mm/slab.c:3719
kmalloc include/linux/slab.h:586 [inline]
sock_kmalloc net/core/sock.c:2501 [inline]
sock_kmalloc+0xb5/0x100 net/core/sock.c:2492
ip_mc_source+0xba2/0x1100 net/ipv4/igmp.c:2392
do_ip_setsockopt net/ipv4/ip_sockglue.c:1296 [inline]
ip_setsockopt+0x2312/0x3ab0 net/ipv4/ip_sockglue.c:1432
raw_setsockopt+0x274/0x2c0 net/ipv4/raw.c:861
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Freed by task 753:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
kasan_set_track+0x21/0x30 mm/kasan/common.c:45
kasan_set_free_info+0x20/0x30 mm/kasan/generic.c:370
____kasan_slab_free mm/kasan/common.c:366 [inline]
____kasan_slab_free+0x13d/0x180 mm/kasan/common.c:328
kasan_slab_free include/linux/kasan.h:200 [inline]
__cache_free mm/slab.c:3439 [inline]
kmem_cache_free_bulk+0x69/0x460 mm/slab.c:3774
kfree_bulk include/linux/slab.h:437 [inline]
kfree_rcu_work+0x51c/0xa10 kernel/rcu/tree.c:3318
process_one_work+0x996/0x1610 kernel/workqueue.c:2289
worker_thread+0x665/0x1080 kernel/workqueue.c:2436
kthread+0x2e9/0x3a0 kernel/kthread.c:376
ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:298
Last potentially related work creation:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
__kasan_record_aux_stack+0x7e/0x90 mm/kasan/generic.c:348
kvfree_call_rcu+0x74/0x990 kernel/rcu/tree.c:3595
ip_mc_msfilter+0x712/0xb60 net/ipv4/igmp.c:2510
do_ip_setsockopt net/ipv4/ip_sockglue.c:1257 [inline]
ip_setsockopt+0x32e1/0x3ab0 net/ipv4/ip_sockglue.c:1432
raw_setsockopt+0x274/0x2c0 net/ipv4/raw.c:861
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Second to last potentially related work creation:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
__kasan_record_aux_stack+0x7e/0x90 mm/kasan/generic.c:348
call_rcu+0x99/0x790 kernel/rcu/tree.c:3074
mpls_dev_notify+0x552/0x8a0 net/mpls/af_mpls.c:1656
notifier_call_chain+0xb5/0x200 kernel/notifier.c:84
call_netdevice_notifiers_info+0xb5/0x130 net/core/dev.c:1938
call_netdevice_notifiers_extack net/core/dev.c:1976 [inline]
call_netdevice_notifiers net/core/dev.c:1990 [inline]
unregister_netdevice_many+0x92e/0x1890 net/core/dev.c:10751
default_device_exit_batch+0x449/0x590 net/core/dev.c:11245
ops_exit_list+0x125/0x170 net/core/net_namespace.c:167
cleanup_net+0x4ea/0xb00 net/core/net_namespace.c:594
process_one_work+0x996/0x1610 kernel/workqueue.c:2289
worker_thread+0x665/0x1080 kernel/workqueue.c:2436
kthread+0x2e9/0x3a0 kernel/kthread.c:376
ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:298
The buggy address belongs to the object at ffff88807d37b900
which belongs to the cache kmalloc-64 of size 64
The buggy address is located 4 bytes inside of
64-byte region [ffff88807d37b900, ffff88807d37b940)
The buggy address belongs to the physical page:
page:ffffea0001f4dec0 refcount:1 mapcount:0 mapping:0000000000000000 index:0xffff88807d37b180 pfn:0x7d37b
flags: 0xfff00000000200(slab|node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000200 ffff888010c41340 ffffea0001c795c8 ffff888010c40200
raw: ffff88807d37b180 ffff88807d37b000 000000010000001f 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x342040(__GFP_IO|__GFP_NOWARN|__GFP_COMP|__GFP_HARDWALL|__GFP_THISNODE), pid 2963, tgid 2963 (udevd), ts 139732238007, free_ts 139730893262
prep_new_page mm/page_alloc.c:2441 [inline]
get_page_from_freelist+0xba2/0x3e00 mm/page_alloc.c:4182
__alloc_pages+0x1b2/0x500 mm/page_alloc.c:5408
__alloc_pages_node include/linux/gfp.h:587 [inline]
kmem_getpages mm/slab.c:1378 [inline]
cache_grow_begin+0x75/0x350 mm/slab.c:2584
cache_alloc_refill+0x27f/0x380 mm/slab.c:2957
____cache_alloc mm/slab.c:3040 [inline]
____cache_alloc mm/slab.c:3023 [inline]
__do_cache_alloc mm/slab.c:3267 [inline]
slab_alloc mm/slab.c:3309 [inline]
__do_kmalloc mm/slab.c:3708 [inline]
__kmalloc+0x3b3/0x4d0 mm/slab.c:3719
kmalloc include/linux/slab.h:586 [inline]
kzalloc include/linux/slab.h:714 [inline]
tomoyo_encode2.part.0+0xe9/0x3a0 security/tomoyo/realpath.c:45
tomoyo_encode2 security/tomoyo/realpath.c:31 [inline]
tomoyo_encode+0x28/0x50 security/tomoyo/realpath.c:80
tomoyo_realpath_from_path+0x186/0x620 security/tomoyo/realpath.c:288
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_path_perm+0x21b/0x400 security/tomoyo/file.c:822
security_inode_getattr+0xcf/0x140 security/security.c:1350
vfs_getattr fs/stat.c:157 [inline]
vfs_statx+0x16a/0x390 fs/stat.c:232
vfs_fstatat+0x8c/0xb0 fs/stat.c:255
__do_sys_newfstatat+0x91/0x110 fs/stat.c:425
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
page last free stack trace:
reset_page_owner include/linux/page_owner.h:24 [inline]
free_pages_prepare mm/page_alloc.c:1356 [inline]
free_pcp_prepare+0x549/0xd20 mm/page_alloc.c:1406
free_unref_page_prepare mm/page_alloc.c:3328 [inline]
free_unref_page+0x19/0x6a0 mm/page_alloc.c:3423
__vunmap+0x85d/0xd30 mm/vmalloc.c:2667
__vfree+0x3c/0xd0 mm/vmalloc.c:2715
vfree+0x5a/0x90 mm/vmalloc.c:2746
__do_replace+0x16b/0x890 net/ipv6/netfilter/ip6_tables.c:1117
do_replace net/ipv6/netfilter/ip6_tables.c:1157 [inline]
do_ip6t_set_ctl+0x90d/0xb90 net/ipv6/netfilter/ip6_tables.c:1639
nf_setsockopt+0x83/0xe0 net/netfilter/nf_sockopt.c:101
ipv6_setsockopt+0x122/0x180 net/ipv6/ipv6_sockglue.c:1026
tcp_setsockopt+0x136/0x2520 net/ipv4/tcp.c:3696
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Memory state around the buggy address:
ffff88807d37b800: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
ffff88807d37b880: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
>ffff88807d37b900: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
^
ffff88807d37b980: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff88807d37ba00: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
Fixes: c85bb41e9318 ("igmp: fix ip_mc_sf_allow race [v5]")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: syzbot <syzkaller@googlegroups.com>
Cc: Flavio Leitner <fbl@sysclose.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-04-29 18:42:57 +03:00
|
|
|
}
|
2012-01-12 08:41:32 +04:00
|
|
|
rcu_assign_pointer(pmc->sflist, newpsl);
|
net: igmp: respect RCU rules in ip_mc_source() and ip_mc_msfilter()
commit dba5bdd57bea587ea4f0b79b03c71135f84a7e8b upstream.
syzbot reported an UAF in ip_mc_sf_allow() [1]
Whenever RCU protected list replaces an object,
the pointer to the new object needs to be updated
_before_ the call to kfree_rcu() or call_rcu()
Because kfree_rcu(ptr, rcu) got support for NULL ptr
only recently in commit 12edff045bc6 ("rcu: Make kfree_rcu()
ignore NULL pointers"), I chose to use the conditional
to make sure stable backports won't miss this detail.
if (psl)
kfree_rcu(psl, rcu);
net/ipv6/mcast.c has similar issues, addressed in a separate patch.
[1]
BUG: KASAN: use-after-free in ip_mc_sf_allow+0x6bb/0x6d0 net/ipv4/igmp.c:2655
Read of size 4 at addr ffff88807d37b904 by task syz-executor.5/908
CPU: 0 PID: 908 Comm: syz-executor.5 Not tainted 5.18.0-rc4-syzkaller-00064-g8f4dd16603ce #0
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:88 [inline]
dump_stack_lvl+0xcd/0x134 lib/dump_stack.c:106
print_address_description.constprop.0.cold+0xeb/0x467 mm/kasan/report.c:313
print_report mm/kasan/report.c:429 [inline]
kasan_report.cold+0xf4/0x1c6 mm/kasan/report.c:491
ip_mc_sf_allow+0x6bb/0x6d0 net/ipv4/igmp.c:2655
raw_v4_input net/ipv4/raw.c:190 [inline]
raw_local_deliver+0x4d1/0xbe0 net/ipv4/raw.c:218
ip_protocol_deliver_rcu+0xcf/0xb30 net/ipv4/ip_input.c:193
ip_local_deliver_finish+0x2ee/0x4c0 net/ipv4/ip_input.c:233
NF_HOOK include/linux/netfilter.h:307 [inline]
NF_HOOK include/linux/netfilter.h:301 [inline]
ip_local_deliver+0x1b3/0x200 net/ipv4/ip_input.c:254
dst_input include/net/dst.h:461 [inline]
ip_rcv_finish+0x1cb/0x2f0 net/ipv4/ip_input.c:437
NF_HOOK include/linux/netfilter.h:307 [inline]
NF_HOOK include/linux/netfilter.h:301 [inline]
ip_rcv+0xaa/0xd0 net/ipv4/ip_input.c:556
__netif_receive_skb_one_core+0x114/0x180 net/core/dev.c:5405
__netif_receive_skb+0x24/0x1b0 net/core/dev.c:5519
netif_receive_skb_internal net/core/dev.c:5605 [inline]
netif_receive_skb+0x13e/0x8e0 net/core/dev.c:5664
tun_rx_batched.isra.0+0x460/0x720 drivers/net/tun.c:1534
tun_get_user+0x28b7/0x3e30 drivers/net/tun.c:1985
tun_chr_write_iter+0xdb/0x200 drivers/net/tun.c:2015
call_write_iter include/linux/fs.h:2050 [inline]
new_sync_write+0x38a/0x560 fs/read_write.c:504
vfs_write+0x7c0/0xac0 fs/read_write.c:591
ksys_write+0x127/0x250 fs/read_write.c:644
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
RIP: 0033:0x7f3f12c3bbff
Code: 89 54 24 18 48 89 74 24 10 89 7c 24 08 e8 99 fd ff ff 48 8b 54 24 18 48 8b 74 24 10 41 89 c0 8b 7c 24 08 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 31 44 89 c7 48 89 44 24 08 e8 cc fd ff ff 48
RSP: 002b:00007f3f13ea9130 EFLAGS: 00000293 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007f3f12d9bf60 RCX: 00007f3f12c3bbff
RDX: 0000000000000036 RSI: 0000000020002ac0 RDI: 00000000000000c8
RBP: 00007f3f12ce308d R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000036 R11: 0000000000000293 R12: 0000000000000000
R13: 00007fffb68dd79f R14: 00007f3f13ea9300 R15: 0000000000022000
</TASK>
Allocated by task 908:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
kasan_set_track mm/kasan/common.c:45 [inline]
set_alloc_info mm/kasan/common.c:436 [inline]
____kasan_kmalloc mm/kasan/common.c:515 [inline]
____kasan_kmalloc mm/kasan/common.c:474 [inline]
__kasan_kmalloc+0xa6/0xd0 mm/kasan/common.c:524
kasan_kmalloc include/linux/kasan.h:234 [inline]
__do_kmalloc mm/slab.c:3710 [inline]
__kmalloc+0x209/0x4d0 mm/slab.c:3719
kmalloc include/linux/slab.h:586 [inline]
sock_kmalloc net/core/sock.c:2501 [inline]
sock_kmalloc+0xb5/0x100 net/core/sock.c:2492
ip_mc_source+0xba2/0x1100 net/ipv4/igmp.c:2392
do_ip_setsockopt net/ipv4/ip_sockglue.c:1296 [inline]
ip_setsockopt+0x2312/0x3ab0 net/ipv4/ip_sockglue.c:1432
raw_setsockopt+0x274/0x2c0 net/ipv4/raw.c:861
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Freed by task 753:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
kasan_set_track+0x21/0x30 mm/kasan/common.c:45
kasan_set_free_info+0x20/0x30 mm/kasan/generic.c:370
____kasan_slab_free mm/kasan/common.c:366 [inline]
____kasan_slab_free+0x13d/0x180 mm/kasan/common.c:328
kasan_slab_free include/linux/kasan.h:200 [inline]
__cache_free mm/slab.c:3439 [inline]
kmem_cache_free_bulk+0x69/0x460 mm/slab.c:3774
kfree_bulk include/linux/slab.h:437 [inline]
kfree_rcu_work+0x51c/0xa10 kernel/rcu/tree.c:3318
process_one_work+0x996/0x1610 kernel/workqueue.c:2289
worker_thread+0x665/0x1080 kernel/workqueue.c:2436
kthread+0x2e9/0x3a0 kernel/kthread.c:376
ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:298
Last potentially related work creation:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
__kasan_record_aux_stack+0x7e/0x90 mm/kasan/generic.c:348
kvfree_call_rcu+0x74/0x990 kernel/rcu/tree.c:3595
ip_mc_msfilter+0x712/0xb60 net/ipv4/igmp.c:2510
do_ip_setsockopt net/ipv4/ip_sockglue.c:1257 [inline]
ip_setsockopt+0x32e1/0x3ab0 net/ipv4/ip_sockglue.c:1432
raw_setsockopt+0x274/0x2c0 net/ipv4/raw.c:861
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Second to last potentially related work creation:
kasan_save_stack+0x1e/0x40 mm/kasan/common.c:38
__kasan_record_aux_stack+0x7e/0x90 mm/kasan/generic.c:348
call_rcu+0x99/0x790 kernel/rcu/tree.c:3074
mpls_dev_notify+0x552/0x8a0 net/mpls/af_mpls.c:1656
notifier_call_chain+0xb5/0x200 kernel/notifier.c:84
call_netdevice_notifiers_info+0xb5/0x130 net/core/dev.c:1938
call_netdevice_notifiers_extack net/core/dev.c:1976 [inline]
call_netdevice_notifiers net/core/dev.c:1990 [inline]
unregister_netdevice_many+0x92e/0x1890 net/core/dev.c:10751
default_device_exit_batch+0x449/0x590 net/core/dev.c:11245
ops_exit_list+0x125/0x170 net/core/net_namespace.c:167
cleanup_net+0x4ea/0xb00 net/core/net_namespace.c:594
process_one_work+0x996/0x1610 kernel/workqueue.c:2289
worker_thread+0x665/0x1080 kernel/workqueue.c:2436
kthread+0x2e9/0x3a0 kernel/kthread.c:376
ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:298
The buggy address belongs to the object at ffff88807d37b900
which belongs to the cache kmalloc-64 of size 64
The buggy address is located 4 bytes inside of
64-byte region [ffff88807d37b900, ffff88807d37b940)
The buggy address belongs to the physical page:
page:ffffea0001f4dec0 refcount:1 mapcount:0 mapping:0000000000000000 index:0xffff88807d37b180 pfn:0x7d37b
flags: 0xfff00000000200(slab|node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000200 ffff888010c41340 ffffea0001c795c8 ffff888010c40200
raw: ffff88807d37b180 ffff88807d37b000 000000010000001f 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x342040(__GFP_IO|__GFP_NOWARN|__GFP_COMP|__GFP_HARDWALL|__GFP_THISNODE), pid 2963, tgid 2963 (udevd), ts 139732238007, free_ts 139730893262
prep_new_page mm/page_alloc.c:2441 [inline]
get_page_from_freelist+0xba2/0x3e00 mm/page_alloc.c:4182
__alloc_pages+0x1b2/0x500 mm/page_alloc.c:5408
__alloc_pages_node include/linux/gfp.h:587 [inline]
kmem_getpages mm/slab.c:1378 [inline]
cache_grow_begin+0x75/0x350 mm/slab.c:2584
cache_alloc_refill+0x27f/0x380 mm/slab.c:2957
____cache_alloc mm/slab.c:3040 [inline]
____cache_alloc mm/slab.c:3023 [inline]
__do_cache_alloc mm/slab.c:3267 [inline]
slab_alloc mm/slab.c:3309 [inline]
__do_kmalloc mm/slab.c:3708 [inline]
__kmalloc+0x3b3/0x4d0 mm/slab.c:3719
kmalloc include/linux/slab.h:586 [inline]
kzalloc include/linux/slab.h:714 [inline]
tomoyo_encode2.part.0+0xe9/0x3a0 security/tomoyo/realpath.c:45
tomoyo_encode2 security/tomoyo/realpath.c:31 [inline]
tomoyo_encode+0x28/0x50 security/tomoyo/realpath.c:80
tomoyo_realpath_from_path+0x186/0x620 security/tomoyo/realpath.c:288
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_path_perm+0x21b/0x400 security/tomoyo/file.c:822
security_inode_getattr+0xcf/0x140 security/security.c:1350
vfs_getattr fs/stat.c:157 [inline]
vfs_statx+0x16a/0x390 fs/stat.c:232
vfs_fstatat+0x8c/0xb0 fs/stat.c:255
__do_sys_newfstatat+0x91/0x110 fs/stat.c:425
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
page last free stack trace:
reset_page_owner include/linux/page_owner.h:24 [inline]
free_pages_prepare mm/page_alloc.c:1356 [inline]
free_pcp_prepare+0x549/0xd20 mm/page_alloc.c:1406
free_unref_page_prepare mm/page_alloc.c:3328 [inline]
free_unref_page+0x19/0x6a0 mm/page_alloc.c:3423
__vunmap+0x85d/0xd30 mm/vmalloc.c:2667
__vfree+0x3c/0xd0 mm/vmalloc.c:2715
vfree+0x5a/0x90 mm/vmalloc.c:2746
__do_replace+0x16b/0x890 net/ipv6/netfilter/ip6_tables.c:1117
do_replace net/ipv6/netfilter/ip6_tables.c:1157 [inline]
do_ip6t_set_ctl+0x90d/0xb90 net/ipv6/netfilter/ip6_tables.c:1639
nf_setsockopt+0x83/0xe0 net/netfilter/nf_sockopt.c:101
ipv6_setsockopt+0x122/0x180 net/ipv6/ipv6_sockglue.c:1026
tcp_setsockopt+0x136/0x2520 net/ipv4/tcp.c:3696
__sys_setsockopt+0x2db/0x6a0 net/socket.c:2180
__do_sys_setsockopt net/socket.c:2191 [inline]
__se_sys_setsockopt net/socket.c:2188 [inline]
__x64_sys_setsockopt+0xba/0x150 net/socket.c:2188
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0xae
Memory state around the buggy address:
ffff88807d37b800: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
ffff88807d37b880: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
>ffff88807d37b900: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
^
ffff88807d37b980: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff88807d37ba00: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
Fixes: c85bb41e9318 ("igmp: fix ip_mc_sf_allow race [v5]")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: syzbot <syzkaller@googlegroups.com>
Cc: Flavio Leitner <fbl@sysclose.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2022-04-29 18:42:57 +03:00
|
|
|
if (psl)
|
|
|
|
kfree_rcu(psl, rcu);
|
2005-04-17 02:20:36 +04:00
|
|
|
pmc->sfmode = msf->imsf_fmode;
|
2005-07-09 04:45:16 +04:00
|
|
|
err = 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
done:
|
2005-07-09 04:47:28 +04:00
|
|
|
if (leavegroup)
|
|
|
|
err = ip_mc_leave_group(sk, &imr);
|
2005-04-17 02:20:36 +04:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
int ip_mc_msfget(struct sock *sk, struct ip_msfilter *msf,
|
2022-09-02 03:28:28 +03:00
|
|
|
sockptr_t optval, sockptr_t optlen)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2022-09-02 03:28:28 +03:00
|
|
|
int err, len, count, copycount, msf_size;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct ip_mreqn imr;
|
2006-09-28 05:31:32 +04:00
|
|
|
__be32 addr = msf->imsf_multiaddr;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct ip_mc_socklist *pmc;
|
|
|
|
struct in_device *in_dev;
|
|
|
|
struct inet_sock *inet = inet_sk(sk);
|
|
|
|
struct ip_sf_socklist *psl;
|
2008-08-14 03:15:57 +04:00
|
|
|
struct net *net = sock_net(sk);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2015-11-04 02:41:16 +03:00
|
|
|
ASSERT_RTNL();
|
|
|
|
|
2007-12-17 00:45:43 +03:00
|
|
|
if (!ipv4_is_multicast(addr))
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
imr.imr_multiaddr.s_addr = msf->imsf_multiaddr;
|
|
|
|
imr.imr_address.s_addr = msf->imsf_interface;
|
|
|
|
imr.imr_ifindex = 0;
|
2008-08-14 03:15:57 +04:00
|
|
|
in_dev = ip_mc_find_dev(net, &imr);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (!in_dev) {
|
|
|
|
err = -ENODEV;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
err = -EADDRNOTAVAIL;
|
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
for_each_pmc_rtnl(inet, pmc) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc->multi.imr_multiaddr.s_addr == msf->imsf_multiaddr &&
|
|
|
|
pmc->multi.imr_ifindex == imr.imr_ifindex)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!pmc) /* must have a prior join */
|
|
|
|
goto done;
|
|
|
|
msf->imsf_fmode = pmc->sfmode;
|
2010-11-12 08:46:50 +03:00
|
|
|
psl = rtnl_dereference(pmc->sflist);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!psl) {
|
|
|
|
len = 0;
|
|
|
|
count = 0;
|
|
|
|
} else {
|
|
|
|
count = psl->sl_count;
|
|
|
|
}
|
|
|
|
copycount = count < msf->imsf_numsrc ? count : msf->imsf_numsrc;
|
2021-07-31 20:08:30 +03:00
|
|
|
len = flex_array_size(psl, sl_addr, copycount);
|
2005-04-17 02:20:36 +04:00
|
|
|
msf->imsf_numsrc = count;
|
2022-09-02 03:28:28 +03:00
|
|
|
msf_size = IP_MSFILTER_SIZE(copycount);
|
|
|
|
if (copy_to_sockptr(optlen, &msf_size, sizeof(int)) ||
|
|
|
|
copy_to_sockptr(optval, msf, IP_MSFILTER_SIZE(0))) {
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EFAULT;
|
|
|
|
}
|
|
|
|
if (len &&
|
2022-09-02 03:28:28 +03:00
|
|
|
copy_to_sockptr_offset(optval,
|
|
|
|
offsetof(struct ip_msfilter, imsf_slist_flex),
|
|
|
|
psl->sl_addr, len))
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
done:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ip_mc_gsfget(struct sock *sk, struct group_filter *gsf,
|
2022-09-02 03:28:28 +03:00
|
|
|
sockptr_t optval, size_t ss_offset)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2020-03-30 00:18:30 +03:00
|
|
|
int i, count, copycount;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct sockaddr_in *psin;
|
2006-09-28 05:31:32 +04:00
|
|
|
__be32 addr;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct ip_mc_socklist *pmc;
|
|
|
|
struct inet_sock *inet = inet_sk(sk);
|
|
|
|
struct ip_sf_socklist *psl;
|
|
|
|
|
2015-11-04 02:41:16 +03:00
|
|
|
ASSERT_RTNL();
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
psin = (struct sockaddr_in *)&gsf->gf_group;
|
|
|
|
if (psin->sin_family != AF_INET)
|
|
|
|
return -EINVAL;
|
|
|
|
addr = psin->sin_addr.s_addr;
|
2007-12-17 00:45:43 +03:00
|
|
|
if (!ipv4_is_multicast(addr))
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
for_each_pmc_rtnl(inet, pmc) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc->multi.imr_multiaddr.s_addr == addr &&
|
|
|
|
pmc->multi.imr_ifindex == gsf->gf_interface)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!pmc) /* must have a prior join */
|
2020-03-30 00:18:30 +03:00
|
|
|
return -EADDRNOTAVAIL;
|
2005-04-17 02:20:36 +04:00
|
|
|
gsf->gf_fmode = pmc->sfmode;
|
2010-11-12 08:46:50 +03:00
|
|
|
psl = rtnl_dereference(pmc->sflist);
|
2005-04-17 02:20:36 +04:00
|
|
|
count = psl ? psl->sl_count : 0;
|
|
|
|
copycount = count < gsf->gf_numsrc ? count : gsf->gf_numsrc;
|
|
|
|
gsf->gf_numsrc = count;
|
2022-09-02 03:28:28 +03:00
|
|
|
for (i = 0; i < copycount; i++) {
|
2005-04-17 02:20:36 +04:00
|
|
|
struct sockaddr_storage ss;
|
|
|
|
|
|
|
|
psin = (struct sockaddr_in *)&ss;
|
|
|
|
memset(&ss, 0, sizeof(ss));
|
|
|
|
psin->sin_family = AF_INET;
|
|
|
|
psin->sin_addr.s_addr = psl->sl_addr[i];
|
2022-09-02 03:28:28 +03:00
|
|
|
if (copy_to_sockptr_offset(optval, ss_offset,
|
|
|
|
&ss, sizeof(ss)))
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EFAULT;
|
2022-09-02 03:28:28 +03:00
|
|
|
ss_offset += sizeof(ss);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* check if a multicast source filter allows delivery for a given <src,dst,intf>
|
|
|
|
*/
|
2017-08-07 18:44:19 +03:00
|
|
|
int ip_mc_sf_allow(struct sock *sk, __be32 loc_addr, __be32 rmt_addr,
|
|
|
|
int dif, int sdif)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct inet_sock *inet = inet_sk(sk);
|
|
|
|
struct ip_mc_socklist *pmc;
|
|
|
|
struct ip_sf_socklist *psl;
|
|
|
|
int i;
|
2010-02-02 18:32:29 +03:00
|
|
|
int ret;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2010-02-02 18:32:29 +03:00
|
|
|
ret = 1;
|
2007-12-17 00:45:43 +03:00
|
|
|
if (!ipv4_is_multicast(loc_addr))
|
2010-02-02 18:32:29 +03:00
|
|
|
goto out;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2010-02-02 18:32:29 +03:00
|
|
|
rcu_read_lock();
|
2010-11-12 08:46:50 +03:00
|
|
|
for_each_pmc_rcu(inet, pmc) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc->multi.imr_multiaddr.s_addr == loc_addr &&
|
2017-08-07 18:44:19 +03:00
|
|
|
(pmc->multi.imr_ifindex == dif ||
|
|
|
|
(sdif && pmc->multi.imr_ifindex == sdif)))
|
2005-04-17 02:20:36 +04:00
|
|
|
break;
|
|
|
|
}
|
2010-02-02 18:32:29 +03:00
|
|
|
ret = inet->mc_all;
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!pmc)
|
2010-02-02 18:32:29 +03:00
|
|
|
goto unlock;
|
2010-11-12 08:46:50 +03:00
|
|
|
psl = rcu_dereference(pmc->sflist);
|
2010-02-02 18:32:29 +03:00
|
|
|
ret = (pmc->sfmode == MCAST_EXCLUDE);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!psl)
|
2010-02-02 18:32:29 +03:00
|
|
|
goto unlock;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2013-12-23 10:37:29 +04:00
|
|
|
for (i = 0; i < psl->sl_count; i++) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (psl->sl_addr[i] == rmt_addr)
|
|
|
|
break;
|
|
|
|
}
|
2010-02-02 18:32:29 +03:00
|
|
|
ret = 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc->sfmode == MCAST_INCLUDE && i >= psl->sl_count)
|
2010-02-02 18:32:29 +03:00
|
|
|
goto unlock;
|
2005-04-17 02:20:36 +04:00
|
|
|
if (pmc->sfmode == MCAST_EXCLUDE && i < psl->sl_count)
|
2010-02-02 18:32:29 +03:00
|
|
|
goto unlock;
|
|
|
|
ret = 1;
|
|
|
|
unlock:
|
|
|
|
rcu_read_unlock();
|
|
|
|
out:
|
|
|
|
return ret;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A socket is closing.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void ip_mc_drop_socket(struct sock *sk)
|
|
|
|
{
|
|
|
|
struct inet_sock *inet = inet_sk(sk);
|
|
|
|
struct ip_mc_socklist *iml;
|
2008-08-14 03:15:57 +04:00
|
|
|
struct net *net = sock_net(sk);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2015-04-03 11:17:26 +03:00
|
|
|
if (!inet->mc_list)
|
2005-04-17 02:20:36 +04:00
|
|
|
return;
|
|
|
|
|
|
|
|
rtnl_lock();
|
2010-11-12 08:46:50 +03:00
|
|
|
while ((iml = rtnl_dereference(inet->mc_list)) != NULL) {
|
2005-04-17 02:20:36 +04:00
|
|
|
struct in_device *in_dev;
|
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
inet->mc_list = iml->next_rcu;
|
2008-08-14 03:15:57 +04:00
|
|
|
in_dev = inetdev_by_index(net, iml->multi.imr_ifindex);
|
2006-08-15 11:20:17 +04:00
|
|
|
(void) ip_mc_leave_src(sk, iml, in_dev);
|
2015-04-03 11:17:27 +03:00
|
|
|
if (in_dev)
|
2005-04-17 02:20:36 +04:00
|
|
|
ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr);
|
2010-02-02 18:32:29 +03:00
|
|
|
/* decrease mem now to avoid the memleak warning */
|
|
|
|
atomic_sub(sizeof(*iml), &sk->sk_omem_alloc);
|
2011-03-18 06:45:08 +03:00
|
|
|
kfree_rcu(iml, rcu);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
rtnl_unlock();
|
|
|
|
}
|
|
|
|
|
2011-03-11 03:34:38 +03:00
|
|
|
/* called with rcu_read_lock() */
|
2015-09-28 21:10:31 +03:00
|
|
|
int ip_check_mc_rcu(struct in_device *in_dev, __be32 mc_addr, __be32 src_addr, u8 proto)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct ip_mc_list *im;
|
2013-06-07 19:48:57 +04:00
|
|
|
struct ip_mc_list __rcu **mc_hash;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct ip_sf_list *psf;
|
|
|
|
int rv = 0;
|
|
|
|
|
2013-06-07 19:48:57 +04:00
|
|
|
mc_hash = rcu_dereference(in_dev->mc_hash);
|
|
|
|
if (mc_hash) {
|
2013-06-13 01:11:16 +04:00
|
|
|
u32 hash = hash_32((__force u32)mc_addr, MC_HASH_SZ_LOG);
|
2013-06-07 19:48:57 +04:00
|
|
|
|
|
|
|
for (im = rcu_dereference(mc_hash[hash]);
|
|
|
|
im != NULL;
|
|
|
|
im = rcu_dereference(im->next_hash)) {
|
|
|
|
if (im->multiaddr == mc_addr)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for_each_pmc_rcu(in_dev, im) {
|
|
|
|
if (im->multiaddr == mc_addr)
|
|
|
|
break;
|
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
if (im && proto == IPPROTO_IGMP) {
|
|
|
|
rv = 1;
|
|
|
|
} else if (im) {
|
|
|
|
if (src_addr) {
|
2021-07-16 07:06:17 +03:00
|
|
|
spin_lock_bh(&im->lock);
|
2013-12-23 10:37:29 +04:00
|
|
|
for (psf = im->sources; psf; psf = psf->sf_next) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (psf->sf_inaddr == src_addr)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (psf)
|
|
|
|
rv = psf->sf_count[MCAST_INCLUDE] ||
|
|
|
|
psf->sf_count[MCAST_EXCLUDE] !=
|
|
|
|
im->sfcount[MCAST_EXCLUDE];
|
|
|
|
else
|
|
|
|
rv = im->sfcount[MCAST_EXCLUDE] != 0;
|
2021-07-16 07:06:17 +03:00
|
|
|
spin_unlock_bh(&im->lock);
|
2005-04-17 02:20:36 +04:00
|
|
|
} else
|
|
|
|
rv = 1; /* unspecified source; tentatively allow */
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(CONFIG_PROC_FS)
|
|
|
|
struct igmp_mc_iter_state {
|
2008-12-26 03:42:51 +03:00
|
|
|
struct seq_net_private p;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct net_device *dev;
|
|
|
|
struct in_device *in_dev;
|
|
|
|
};
|
|
|
|
|
|
|
|
#define igmp_mc_seq_private(seq) ((struct igmp_mc_iter_state *)(seq)->private)
|
|
|
|
|
|
|
|
static inline struct ip_mc_list *igmp_mc_get_first(struct seq_file *seq)
|
|
|
|
{
|
2008-12-26 03:42:51 +03:00
|
|
|
struct net *net = seq_file_net(seq);
|
2005-04-17 02:20:36 +04:00
|
|
|
struct ip_mc_list *im = NULL;
|
|
|
|
struct igmp_mc_iter_state *state = igmp_mc_seq_private(seq);
|
|
|
|
|
2007-05-04 02:13:45 +04:00
|
|
|
state->in_dev = NULL;
|
2009-11-10 10:54:55 +03:00
|
|
|
for_each_netdev_rcu(net, state->dev) {
|
2005-04-17 02:20:36 +04:00
|
|
|
struct in_device *in_dev;
|
2009-11-11 20:48:52 +03:00
|
|
|
|
|
|
|
in_dev = __in_dev_get_rcu(state->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!in_dev)
|
|
|
|
continue;
|
2010-11-12 08:46:50 +03:00
|
|
|
im = rcu_dereference(in_dev->mc_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (im) {
|
|
|
|
state->in_dev = in_dev;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return im;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ip_mc_list *igmp_mc_get_next(struct seq_file *seq, struct ip_mc_list *im)
|
|
|
|
{
|
|
|
|
struct igmp_mc_iter_state *state = igmp_mc_seq_private(seq);
|
2009-11-11 20:48:52 +03:00
|
|
|
|
2010-11-12 08:46:50 +03:00
|
|
|
im = rcu_dereference(im->next_rcu);
|
|
|
|
while (!im) {
|
2009-11-11 20:48:52 +03:00
|
|
|
state->dev = next_net_device_rcu(state->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!state->dev) {
|
|
|
|
state->in_dev = NULL;
|
|
|
|
break;
|
|
|
|
}
|
2009-11-11 20:48:52 +03:00
|
|
|
state->in_dev = __in_dev_get_rcu(state->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!state->in_dev)
|
|
|
|
continue;
|
2010-11-12 08:46:50 +03:00
|
|
|
im = rcu_dereference(state->in_dev->mc_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
return im;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ip_mc_list *igmp_mc_get_idx(struct seq_file *seq, loff_t pos)
|
|
|
|
{
|
|
|
|
struct ip_mc_list *im = igmp_mc_get_first(seq);
|
|
|
|
if (im)
|
|
|
|
while (pos && (im = igmp_mc_get_next(seq, im)) != NULL)
|
|
|
|
--pos;
|
|
|
|
return pos ? NULL : im;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *igmp_mc_seq_start(struct seq_file *seq, loff_t *pos)
|
2009-11-10 10:54:55 +03:00
|
|
|
__acquires(rcu)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2009-11-10 10:54:55 +03:00
|
|
|
rcu_read_lock();
|
2005-04-17 02:20:36 +04:00
|
|
|
return *pos ? igmp_mc_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *igmp_mc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
|
|
{
|
|
|
|
struct ip_mc_list *im;
|
|
|
|
if (v == SEQ_START_TOKEN)
|
|
|
|
im = igmp_mc_get_first(seq);
|
|
|
|
else
|
|
|
|
im = igmp_mc_get_next(seq, v);
|
|
|
|
++*pos;
|
|
|
|
return im;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void igmp_mc_seq_stop(struct seq_file *seq, void *v)
|
2009-11-10 10:54:55 +03:00
|
|
|
__releases(rcu)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct igmp_mc_iter_state *state = igmp_mc_seq_private(seq);
|
2010-11-12 08:46:50 +03:00
|
|
|
|
|
|
|
state->in_dev = NULL;
|
2005-04-17 02:20:36 +04:00
|
|
|
state->dev = NULL;
|
2009-11-10 10:54:55 +03:00
|
|
|
rcu_read_unlock();
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int igmp_mc_seq_show(struct seq_file *seq, void *v)
|
|
|
|
{
|
|
|
|
if (v == SEQ_START_TOKEN)
|
2007-02-09 17:24:47 +03:00
|
|
|
seq_puts(seq,
|
2005-04-17 02:20:36 +04:00
|
|
|
"Idx\tDevice : Count Querier\tGroup Users Timer\tReporter\n");
|
|
|
|
else {
|
|
|
|
struct ip_mc_list *im = (struct ip_mc_list *)v;
|
|
|
|
struct igmp_mc_iter_state *state = igmp_mc_seq_private(seq);
|
|
|
|
char *querier;
|
2012-08-09 01:13:53 +04:00
|
|
|
long delta;
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
|
querier = IGMP_V1_SEEN(state->in_dev) ? "V1" :
|
|
|
|
IGMP_V2_SEEN(state->in_dev) ? "V2" :
|
|
|
|
"V3";
|
|
|
|
#else
|
|
|
|
querier = "NONE";
|
|
|
|
#endif
|
|
|
|
|
2014-08-17 16:49:41 +04:00
|
|
|
if (rcu_access_pointer(state->in_dev->mc_list) == im) {
|
2005-04-17 02:20:36 +04:00
|
|
|
seq_printf(seq, "%d\t%-10s: %5d %7s\n",
|
2008-10-08 02:34:37 +04:00
|
|
|
state->dev->ifindex, state->dev->name, state->in_dev->mc_count, querier);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
2012-08-09 01:13:53 +04:00
|
|
|
delta = im->timer.expires - jiffies;
|
2005-04-17 02:20:36 +04:00
|
|
|
seq_printf(seq,
|
2006-06-06 08:04:39 +04:00
|
|
|
"\t\t\t\t%08X %5d %d:%08lX\t\t%d\n",
|
2005-04-17 02:20:36 +04:00
|
|
|
im->multiaddr, im->users,
|
2012-08-09 01:13:53 +04:00
|
|
|
im->tm_running,
|
|
|
|
im->tm_running ? jiffies_delta_to_clock_t(delta) : 0,
|
2005-04-17 02:20:36 +04:00
|
|
|
im->reporter);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-13 00:34:29 +03:00
|
|
|
static const struct seq_operations igmp_mc_seq_ops = {
|
2005-04-17 02:20:36 +04:00
|
|
|
.start = igmp_mc_seq_start,
|
|
|
|
.next = igmp_mc_seq_next,
|
|
|
|
.stop = igmp_mc_seq_stop,
|
|
|
|
.show = igmp_mc_seq_show,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct igmp_mcf_iter_state {
|
2008-12-26 03:42:51 +03:00
|
|
|
struct seq_net_private p;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct net_device *dev;
|
|
|
|
struct in_device *idev;
|
|
|
|
struct ip_mc_list *im;
|
|
|
|
};
|
|
|
|
|
|
|
|
#define igmp_mcf_seq_private(seq) ((struct igmp_mcf_iter_state *)(seq)->private)
|
|
|
|
|
|
|
|
static inline struct ip_sf_list *igmp_mcf_get_first(struct seq_file *seq)
|
|
|
|
{
|
2008-12-26 03:42:51 +03:00
|
|
|
struct net *net = seq_file_net(seq);
|
2005-04-17 02:20:36 +04:00
|
|
|
struct ip_sf_list *psf = NULL;
|
|
|
|
struct ip_mc_list *im = NULL;
|
|
|
|
struct igmp_mcf_iter_state *state = igmp_mcf_seq_private(seq);
|
|
|
|
|
2007-05-04 02:13:45 +04:00
|
|
|
state->idev = NULL;
|
|
|
|
state->im = NULL;
|
2009-11-10 10:54:55 +03:00
|
|
|
for_each_netdev_rcu(net, state->dev) {
|
2005-04-17 02:20:36 +04:00
|
|
|
struct in_device *idev;
|
2009-11-11 20:48:52 +03:00
|
|
|
idev = __in_dev_get_rcu(state->dev);
|
2015-04-03 11:17:26 +03:00
|
|
|
if (unlikely(!idev))
|
2005-04-17 02:20:36 +04:00
|
|
|
continue;
|
2010-11-12 08:46:50 +03:00
|
|
|
im = rcu_dereference(idev->mc_list);
|
2015-04-03 11:17:27 +03:00
|
|
|
if (likely(im)) {
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock_bh(&im->lock);
|
|
|
|
psf = im->sources;
|
2015-04-03 11:17:27 +03:00
|
|
|
if (likely(psf)) {
|
2005-04-17 02:20:36 +04:00
|
|
|
state->im = im;
|
|
|
|
state->idev = idev;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
spin_unlock_bh(&im->lock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return psf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ip_sf_list *igmp_mcf_get_next(struct seq_file *seq, struct ip_sf_list *psf)
|
|
|
|
{
|
|
|
|
struct igmp_mcf_iter_state *state = igmp_mcf_seq_private(seq);
|
|
|
|
|
|
|
|
psf = psf->sf_next;
|
|
|
|
while (!psf) {
|
|
|
|
spin_unlock_bh(&state->im->lock);
|
|
|
|
state->im = state->im->next;
|
|
|
|
while (!state->im) {
|
2009-11-11 20:48:52 +03:00
|
|
|
state->dev = next_net_device_rcu(state->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!state->dev) {
|
|
|
|
state->idev = NULL;
|
|
|
|
goto out;
|
|
|
|
}
|
2009-11-11 20:48:52 +03:00
|
|
|
state->idev = __in_dev_get_rcu(state->dev);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!state->idev)
|
|
|
|
continue;
|
2010-11-12 08:46:50 +03:00
|
|
|
state->im = rcu_dereference(state->idev->mc_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
if (!state->im)
|
|
|
|
break;
|
|
|
|
spin_lock_bh(&state->im->lock);
|
|
|
|
psf = state->im->sources;
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
return psf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ip_sf_list *igmp_mcf_get_idx(struct seq_file *seq, loff_t pos)
|
|
|
|
{
|
|
|
|
struct ip_sf_list *psf = igmp_mcf_get_first(seq);
|
|
|
|
if (psf)
|
|
|
|
while (pos && (psf = igmp_mcf_get_next(seq, psf)) != NULL)
|
|
|
|
--pos;
|
|
|
|
return pos ? NULL : psf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *igmp_mcf_seq_start(struct seq_file *seq, loff_t *pos)
|
2009-11-10 10:54:55 +03:00
|
|
|
__acquires(rcu)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2009-11-10 10:54:55 +03:00
|
|
|
rcu_read_lock();
|
2005-04-17 02:20:36 +04:00
|
|
|
return *pos ? igmp_mcf_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *igmp_mcf_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
|
|
{
|
|
|
|
struct ip_sf_list *psf;
|
|
|
|
if (v == SEQ_START_TOKEN)
|
|
|
|
psf = igmp_mcf_get_first(seq);
|
|
|
|
else
|
|
|
|
psf = igmp_mcf_get_next(seq, v);
|
|
|
|
++*pos;
|
|
|
|
return psf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void igmp_mcf_seq_stop(struct seq_file *seq, void *v)
|
2009-11-10 10:54:55 +03:00
|
|
|
__releases(rcu)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct igmp_mcf_iter_state *state = igmp_mcf_seq_private(seq);
|
2015-04-03 11:17:27 +03:00
|
|
|
if (likely(state->im)) {
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_unlock_bh(&state->im->lock);
|
|
|
|
state->im = NULL;
|
|
|
|
}
|
2010-11-12 08:46:50 +03:00
|
|
|
state->idev = NULL;
|
2005-04-17 02:20:36 +04:00
|
|
|
state->dev = NULL;
|
2009-11-10 10:54:55 +03:00
|
|
|
rcu_read_unlock();
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int igmp_mcf_seq_show(struct seq_file *seq, void *v)
|
|
|
|
{
|
|
|
|
struct ip_sf_list *psf = (struct ip_sf_list *)v;
|
|
|
|
struct igmp_mcf_iter_state *state = igmp_mcf_seq_private(seq);
|
|
|
|
|
|
|
|
if (v == SEQ_START_TOKEN) {
|
2014-11-05 02:37:03 +03:00
|
|
|
seq_puts(seq, "Idx Device MCA SRC INC EXC\n");
|
2005-04-17 02:20:36 +04:00
|
|
|
} else {
|
|
|
|
seq_printf(seq,
|
|
|
|
"%3d %6.6s 0x%08x "
|
2007-02-09 17:24:47 +03:00
|
|
|
"0x%08x %6lu %6lu\n",
|
|
|
|
state->dev->ifindex, state->dev->name,
|
2005-04-17 02:20:36 +04:00
|
|
|
ntohl(state->im->multiaddr),
|
|
|
|
ntohl(psf->sf_inaddr),
|
|
|
|
psf->sf_count[MCAST_INCLUDE],
|
|
|
|
psf->sf_count[MCAST_EXCLUDE]);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-13 00:34:29 +03:00
|
|
|
static const struct seq_operations igmp_mcf_seq_ops = {
|
2005-04-17 02:20:36 +04:00
|
|
|
.start = igmp_mcf_seq_start,
|
|
|
|
.next = igmp_mcf_seq_next,
|
|
|
|
.stop = igmp_mcf_seq_stop,
|
|
|
|
.show = igmp_mcf_seq_show,
|
|
|
|
};
|
|
|
|
|
2010-01-17 06:35:32 +03:00
|
|
|
static int __net_init igmp_net_init(struct net *net)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2008-12-26 03:42:51 +03:00
|
|
|
struct proc_dir_entry *pde;
|
2015-02-25 20:58:35 +03:00
|
|
|
int err;
|
2008-12-26 03:42:51 +03:00
|
|
|
|
2018-04-10 20:42:55 +03:00
|
|
|
pde = proc_create_net("igmp", 0444, net->proc_net, &igmp_mc_seq_ops,
|
|
|
|
sizeof(struct igmp_mc_iter_state));
|
2008-12-26 03:42:51 +03:00
|
|
|
if (!pde)
|
|
|
|
goto out_igmp;
|
2018-04-10 20:42:55 +03:00
|
|
|
pde = proc_create_net("mcfilter", 0444, net->proc_net,
|
|
|
|
&igmp_mcf_seq_ops, sizeof(struct igmp_mcf_iter_state));
|
2008-12-26 03:42:51 +03:00
|
|
|
if (!pde)
|
|
|
|
goto out_mcfilter;
|
2015-02-25 20:58:35 +03:00
|
|
|
err = inet_ctl_sock_create(&net->ipv4.mc_autojoin_sk, AF_INET,
|
|
|
|
SOCK_DGRAM, 0, net);
|
|
|
|
if (err < 0) {
|
|
|
|
pr_err("Failed to initialize the IGMP autojoin socket (err %d)\n",
|
|
|
|
err);
|
|
|
|
goto out_sock;
|
|
|
|
}
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
return 0;
|
2008-12-26 03:42:51 +03:00
|
|
|
|
2015-02-25 20:58:35 +03:00
|
|
|
out_sock:
|
|
|
|
remove_proc_entry("mcfilter", net->proc_net);
|
2008-12-26 03:42:51 +03:00
|
|
|
out_mcfilter:
|
2013-02-18 05:34:56 +04:00
|
|
|
remove_proc_entry("igmp", net->proc_net);
|
2008-12-26 03:42:51 +03:00
|
|
|
out_igmp:
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2010-01-17 06:35:32 +03:00
|
|
|
static void __net_exit igmp_net_exit(struct net *net)
|
2008-12-26 03:42:51 +03:00
|
|
|
{
|
2013-02-18 05:34:56 +04:00
|
|
|
remove_proc_entry("mcfilter", net->proc_net);
|
|
|
|
remove_proc_entry("igmp", net->proc_net);
|
2015-02-25 20:58:35 +03:00
|
|
|
inet_ctl_sock_destroy(net->ipv4.mc_autojoin_sk);
|
2008-12-26 03:42:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct pernet_operations igmp_net_ops = {
|
|
|
|
.init = igmp_net_init,
|
|
|
|
.exit = igmp_net_exit,
|
|
|
|
};
|
2014-01-11 04:09:45 +04:00
|
|
|
#endif
|
2008-12-26 03:42:51 +03:00
|
|
|
|
2013-07-20 14:13:53 +04:00
|
|
|
static int igmp_netdev_event(struct notifier_block *this,
|
|
|
|
unsigned long event, void *ptr)
|
|
|
|
{
|
|
|
|
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
|
|
|
struct in_device *in_dev;
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case NETDEV_RESEND_IGMP:
|
|
|
|
in_dev = __in_dev_get_rtnl(dev);
|
|
|
|
if (in_dev)
|
|
|
|
ip_mc_rejoin_groups(in_dev);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct notifier_block igmp_notifier = {
|
|
|
|
.notifier_call = igmp_netdev_event,
|
|
|
|
};
|
|
|
|
|
2014-01-11 04:09:45 +04:00
|
|
|
int __init igmp_mc_init(void)
|
2008-12-26 03:42:51 +03:00
|
|
|
{
|
2014-01-11 04:09:45 +04:00
|
|
|
#if defined(CONFIG_PROC_FS)
|
2013-07-20 14:13:53 +04:00
|
|
|
int err;
|
|
|
|
|
|
|
|
err = register_pernet_subsys(&igmp_net_ops);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
err = register_netdevice_notifier(&igmp_notifier);
|
|
|
|
if (err)
|
|
|
|
goto reg_notif_fail;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
reg_notif_fail:
|
|
|
|
unregister_pernet_subsys(&igmp_net_ops);
|
|
|
|
return err;
|
2014-01-11 04:09:45 +04:00
|
|
|
#else
|
|
|
|
return register_netdevice_notifier(&igmp_notifier);
|
2005-04-17 02:20:36 +04:00
|
|
|
#endif
|
2014-01-11 04:09:45 +04:00
|
|
|
}
|