2019-05-27 13:55:01 +07:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* net/sched/cls_api.c Packet classifier API.
|
|
|
|
*
|
|
|
|
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
|
|
|
|
*
|
|
|
|
* Changes:
|
|
|
|
*
|
|
|
|
* Eduardo J. Blanco <ejbs@netlabs.com.uy> :990222: kmod support
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/errno.h>
|
2017-02-09 20:38:57 +07:00
|
|
|
#include <linux/err.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/kmod.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 15:04:11 +07:00
|
|
|
#include <linux/slab.h>
|
2018-01-17 17:46:46 +07:00
|
|
|
#include <linux/idr.h>
|
2018-11-10 12:21:26 +07:00
|
|
|
#include <linux/rhashtable.h>
|
2019-11-02 21:17:47 +07:00
|
|
|
#include <linux/jhash.h>
|
2020-02-16 17:01:23 +07:00
|
|
|
#include <linux/rculist.h>
|
2007-11-30 20:21:31 +07:00
|
|
|
#include <net/net_namespace.h>
|
|
|
|
#include <net/sock.h>
|
2007-03-26 13:06:12 +07:00
|
|
|
#include <net/netlink.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <net/pkt_sched.h>
|
|
|
|
#include <net/pkt_cls.h>
|
2019-02-02 18:50:45 +07:00
|
|
|
#include <net/tc_act/tc_pedit.h>
|
2019-02-02 18:50:46 +07:00
|
|
|
#include <net/tc_act/tc_mirred.h>
|
|
|
|
#include <net/tc_act/tc_vlan.h>
|
|
|
|
#include <net/tc_act/tc_tunnel_key.h>
|
|
|
|
#include <net/tc_act/tc_csum.h>
|
|
|
|
#include <net/tc_act/tc_gact.h>
|
2019-05-04 18:46:22 +07:00
|
|
|
#include <net/tc_act/tc_police.h>
|
2019-05-04 18:46:16 +07:00
|
|
|
#include <net/tc_act/tc_sample.h>
|
2019-02-02 18:50:46 +07:00
|
|
|
#include <net/tc_act/tc_skbedit.h>
|
2019-07-09 14:30:48 +07:00
|
|
|
#include <net/tc_act/tc_ct.h>
|
2019-07-23 21:33:59 +07:00
|
|
|
#include <net/tc_act/tc_mpls.h>
|
2020-05-01 07:53:16 +07:00
|
|
|
#include <net/tc_act/tc_gate.h>
|
2019-08-07 08:13:52 +07:00
|
|
|
#include <net/flow_offload.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2018-10-11 03:00:58 +07:00
|
|
|
extern const struct nla_policy rtm_tca_policy[TCA_MAX + 1];
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/* The list of all installed classifier types */
|
2013-12-16 11:15:11 +07:00
|
|
|
static LIST_HEAD(tcf_proto_base);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* Protects list of registered TC modules. It is pure SMP lock. */
|
|
|
|
static DEFINE_RWLOCK(cls_mod_lock);
|
|
|
|
|
2019-11-02 21:17:47 +07:00
|
|
|
static u32 destroy_obj_hashfn(const struct tcf_proto *tp)
|
|
|
|
{
|
|
|
|
return jhash_3words(tp->chain->index, tp->prio,
|
|
|
|
(__force __u32)tp->protocol, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_proto_signal_destroying(struct tcf_chain *chain,
|
|
|
|
struct tcf_proto *tp)
|
|
|
|
{
|
|
|
|
struct tcf_block *block = chain->block;
|
|
|
|
|
|
|
|
mutex_lock(&block->proto_destroy_lock);
|
|
|
|
hash_add_rcu(block->proto_destroy_ht, &tp->destroy_ht_node,
|
|
|
|
destroy_obj_hashfn(tp));
|
|
|
|
mutex_unlock(&block->proto_destroy_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool tcf_proto_cmp(const struct tcf_proto *tp1,
|
|
|
|
const struct tcf_proto *tp2)
|
|
|
|
{
|
|
|
|
return tp1->chain->index == tp2->chain->index &&
|
|
|
|
tp1->prio == tp2->prio &&
|
|
|
|
tp1->protocol == tp2->protocol;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool tcf_proto_exists_destroying(struct tcf_chain *chain,
|
|
|
|
struct tcf_proto *tp)
|
|
|
|
{
|
|
|
|
u32 hash = destroy_obj_hashfn(tp);
|
|
|
|
struct tcf_proto *iter;
|
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
rcu_read_lock();
|
|
|
|
hash_for_each_possible_rcu(chain->block->proto_destroy_ht, iter,
|
|
|
|
destroy_ht_node, hash) {
|
|
|
|
if (tcf_proto_cmp(tp, iter)) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
|
|
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tcf_proto_signal_destroyed(struct tcf_chain *chain, struct tcf_proto *tp)
|
|
|
|
{
|
|
|
|
struct tcf_block *block = chain->block;
|
|
|
|
|
|
|
|
mutex_lock(&block->proto_destroy_lock);
|
|
|
|
if (hash_hashed(&tp->destroy_ht_node))
|
|
|
|
hash_del_rcu(&tp->destroy_ht_node);
|
|
|
|
mutex_unlock(&block->proto_destroy_lock);
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/* Find classifier type by string name */
|
|
|
|
|
2018-07-23 14:23:04 +07:00
|
|
|
static const struct tcf_proto_ops *__tcf_proto_lookup_ops(const char *kind)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2013-12-21 01:04:18 +07:00
|
|
|
const struct tcf_proto_ops *t, *res = NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (kind) {
|
|
|
|
read_lock(&cls_mod_lock);
|
2013-12-16 11:15:11 +07:00
|
|
|
list_for_each_entry(t, &tcf_proto_base, head) {
|
2017-02-09 20:38:57 +07:00
|
|
|
if (strcmp(kind, t->kind) == 0) {
|
2013-12-21 01:04:18 +07:00
|
|
|
if (try_module_get(t->owner))
|
|
|
|
res = t;
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
read_unlock(&cls_mod_lock);
|
|
|
|
}
|
2013-12-21 01:04:18 +07:00
|
|
|
return res;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2018-07-23 14:23:04 +07:00
|
|
|
static const struct tcf_proto_ops *
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_lookup_ops(const char *kind, bool rtnl_held,
|
|
|
|
struct netlink_ext_ack *extack)
|
2018-07-23 14:23:04 +07:00
|
|
|
{
|
|
|
|
const struct tcf_proto_ops *ops;
|
|
|
|
|
|
|
|
ops = __tcf_proto_lookup_ops(kind);
|
|
|
|
if (ops)
|
|
|
|
return ops;
|
|
|
|
#ifdef CONFIG_MODULES
|
2019-02-11 15:55:45 +07:00
|
|
|
if (rtnl_held)
|
|
|
|
rtnl_unlock();
|
2018-07-23 14:23:04 +07:00
|
|
|
request_module("cls_%s", kind);
|
2019-02-11 15:55:45 +07:00
|
|
|
if (rtnl_held)
|
|
|
|
rtnl_lock();
|
2018-07-23 14:23:04 +07:00
|
|
|
ops = __tcf_proto_lookup_ops(kind);
|
|
|
|
/* We dropped the RTNL semaphore in order to perform
|
|
|
|
* the module load. So, even if we succeeded in loading
|
|
|
|
* the module we have to replay the request. We indicate
|
|
|
|
* this using -EAGAIN.
|
|
|
|
*/
|
|
|
|
if (ops) {
|
|
|
|
module_put(ops->owner);
|
|
|
|
return ERR_PTR(-EAGAIN);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
NL_SET_ERR_MSG(extack, "TC classifier not found");
|
|
|
|
return ERR_PTR(-ENOENT);
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/* Register(unregister) new classifier type */
|
|
|
|
|
|
|
|
int register_tcf_proto_ops(struct tcf_proto_ops *ops)
|
|
|
|
{
|
2013-12-16 11:15:11 +07:00
|
|
|
struct tcf_proto_ops *t;
|
2005-04-17 05:20:36 +07:00
|
|
|
int rc = -EEXIST;
|
|
|
|
|
|
|
|
write_lock(&cls_mod_lock);
|
2013-12-16 11:15:11 +07:00
|
|
|
list_for_each_entry(t, &tcf_proto_base, head)
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!strcmp(ops->kind, t->kind))
|
|
|
|
goto out;
|
|
|
|
|
2013-12-16 11:15:11 +07:00
|
|
|
list_add_tail(&ops->head, &tcf_proto_base);
|
2005-04-17 05:20:36 +07:00
|
|
|
rc = 0;
|
|
|
|
out:
|
|
|
|
write_unlock(&cls_mod_lock);
|
|
|
|
return rc;
|
|
|
|
}
|
2008-01-21 17:26:41 +07:00
|
|
|
EXPORT_SYMBOL(register_tcf_proto_ops);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2017-10-27 08:24:28 +07:00
|
|
|
static struct workqueue_struct *tc_filter_wq;
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
int unregister_tcf_proto_ops(struct tcf_proto_ops *ops)
|
|
|
|
{
|
2013-12-16 11:15:11 +07:00
|
|
|
struct tcf_proto_ops *t;
|
2005-04-17 05:20:36 +07:00
|
|
|
int rc = -ENOENT;
|
|
|
|
|
net: sched: fix call_rcu() race on classifier module unloads
Vijay reported that a loop as simple as ...
while true; do
tc qdisc add dev foo root handle 1: prio
tc filter add dev foo parent 1: u32 match u32 0 0 flowid 1
tc qdisc del dev foo root
rmmod cls_u32
done
... will panic the kernel. Moreover, he bisected the change
apparently introducing it to 78fd1d0ab072 ("netlink: Re-add
locking to netlink_lookup() and seq walker").
The removal of synchronize_net() from the netlink socket
triggering the qdisc to be removed, seems to have uncovered
an RCU resp. module reference count race from the tc API.
Given that RCU conversion was done after e341694e3eb5 ("netlink:
Convert netlink_lookup() to use RCU protected hash table")
which added the synchronize_net() originally, occasion of
hitting the bug was less likely (not impossible though):
When qdiscs that i) support attaching classifiers and,
ii) have at least one of them attached, get deleted, they
invoke tcf_destroy_chain(), and thus call into ->destroy()
handler from a classifier module.
After RCU conversion, all classifier that have an internal
prio list, unlink them and initiate freeing via call_rcu()
deferral.
Meanhile, tcf_destroy() releases already reference to the
tp->ops->owner module before the queued RCU callback handler
has been invoked.
Subsequent rmmod on the classifier module is then not prevented
since all module references are already dropped.
By the time, the kernel invokes the RCU callback handler from
the module, that function address is then invalid.
One way to fix it would be to add an rcu_barrier() to
unregister_tcf_proto_ops() to wait for all pending call_rcu()s
to complete.
synchronize_rcu() is not appropriate as under heavy RCU
callback load, registered call_rcu()s could be deferred
longer than a grace period. In case we don't have any pending
call_rcu()s, the barrier is allowed to return immediately.
Since we came here via unregister_tcf_proto_ops(), there
are no users of a given classifier anymore. Further nested
call_rcu()s pointing into the module space are not being
done anywhere.
Only cls_bpf_delete_prog() may schedule a work item, to
unlock pages eventually, but that is not in the range/context
of cls_bpf anymore.
Fixes: 25d8c0d55f24 ("net: rcu-ify tcf_proto")
Fixes: 9888faefe132 ("net: sched: cls_basic use RCU")
Reported-by: Vijay Subramanian <subramanian.vijay@gmail.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Cc: John Fastabend <john.r.fastabend@intel.com>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Thomas Graf <tgraf@suug.ch>
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Cc: Alexei Starovoitov <ast@plumgrid.com>
Tested-by: Vijay Subramanian <subramanian.vijay@gmail.com>
Acked-by: Alexei Starovoitov <ast@plumgrid.com>
Acked-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-05-20 22:13:33 +07:00
|
|
|
/* Wait for outstanding call_rcu()s, if any, from a
|
|
|
|
* tcf_proto_ops's destroy() handler.
|
|
|
|
*/
|
|
|
|
rcu_barrier();
|
2017-10-27 08:24:28 +07:00
|
|
|
flush_workqueue(tc_filter_wq);
|
net: sched: fix call_rcu() race on classifier module unloads
Vijay reported that a loop as simple as ...
while true; do
tc qdisc add dev foo root handle 1: prio
tc filter add dev foo parent 1: u32 match u32 0 0 flowid 1
tc qdisc del dev foo root
rmmod cls_u32
done
... will panic the kernel. Moreover, he bisected the change
apparently introducing it to 78fd1d0ab072 ("netlink: Re-add
locking to netlink_lookup() and seq walker").
The removal of synchronize_net() from the netlink socket
triggering the qdisc to be removed, seems to have uncovered
an RCU resp. module reference count race from the tc API.
Given that RCU conversion was done after e341694e3eb5 ("netlink:
Convert netlink_lookup() to use RCU protected hash table")
which added the synchronize_net() originally, occasion of
hitting the bug was less likely (not impossible though):
When qdiscs that i) support attaching classifiers and,
ii) have at least one of them attached, get deleted, they
invoke tcf_destroy_chain(), and thus call into ->destroy()
handler from a classifier module.
After RCU conversion, all classifier that have an internal
prio list, unlink them and initiate freeing via call_rcu()
deferral.
Meanhile, tcf_destroy() releases already reference to the
tp->ops->owner module before the queued RCU callback handler
has been invoked.
Subsequent rmmod on the classifier module is then not prevented
since all module references are already dropped.
By the time, the kernel invokes the RCU callback handler from
the module, that function address is then invalid.
One way to fix it would be to add an rcu_barrier() to
unregister_tcf_proto_ops() to wait for all pending call_rcu()s
to complete.
synchronize_rcu() is not appropriate as under heavy RCU
callback load, registered call_rcu()s could be deferred
longer than a grace period. In case we don't have any pending
call_rcu()s, the barrier is allowed to return immediately.
Since we came here via unregister_tcf_proto_ops(), there
are no users of a given classifier anymore. Further nested
call_rcu()s pointing into the module space are not being
done anywhere.
Only cls_bpf_delete_prog() may schedule a work item, to
unlock pages eventually, but that is not in the range/context
of cls_bpf anymore.
Fixes: 25d8c0d55f24 ("net: rcu-ify tcf_proto")
Fixes: 9888faefe132 ("net: sched: cls_basic use RCU")
Reported-by: Vijay Subramanian <subramanian.vijay@gmail.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Cc: John Fastabend <john.r.fastabend@intel.com>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Thomas Graf <tgraf@suug.ch>
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Cc: Alexei Starovoitov <ast@plumgrid.com>
Tested-by: Vijay Subramanian <subramanian.vijay@gmail.com>
Acked-by: Alexei Starovoitov <ast@plumgrid.com>
Acked-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-05-20 22:13:33 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
write_lock(&cls_mod_lock);
|
2013-12-21 01:04:18 +07:00
|
|
|
list_for_each_entry(t, &tcf_proto_base, head) {
|
|
|
|
if (t == ops) {
|
|
|
|
list_del(&t->head);
|
|
|
|
rc = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
2013-12-21 01:04:18 +07:00
|
|
|
}
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
write_unlock(&cls_mod_lock);
|
|
|
|
return rc;
|
|
|
|
}
|
2008-01-21 17:26:41 +07:00
|
|
|
EXPORT_SYMBOL(unregister_tcf_proto_ops);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2018-05-24 05:26:53 +07:00
|
|
|
bool tcf_queue_work(struct rcu_work *rwork, work_func_t func)
|
2017-10-27 08:24:28 +07:00
|
|
|
{
|
2018-05-24 05:26:53 +07:00
|
|
|
INIT_RCU_WORK(rwork, func);
|
|
|
|
return queue_rcu_work(tc_filter_wq, rwork);
|
2017-10-27 08:24:28 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tcf_queue_work);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/* Select new prio value from the range, managed by kernel. */
|
|
|
|
|
2008-01-21 17:26:41 +07:00
|
|
|
static inline u32 tcf_auto_prio(struct tcf_proto *tp)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2008-01-21 17:26:41 +07:00
|
|
|
u32 first = TC_H_MAKE(0xC0000000U, 0U);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (tp)
|
2011-01-20 02:26:56 +07:00
|
|
|
first = tp->prio - 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2017-05-17 16:07:58 +07:00
|
|
|
return TC_H_MAJ(first);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2019-10-08 03:26:28 +07:00
|
|
|
static bool tcf_proto_check_kind(struct nlattr *kind, char *name)
|
|
|
|
{
|
|
|
|
if (kind)
|
|
|
|
return nla_strlcpy(name, kind, IFNAMSIZ) >= IFNAMSIZ;
|
|
|
|
memset(name, 0, IFNAMSIZ);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:48 +07:00
|
|
|
static bool tcf_proto_is_unlocked(const char *kind)
|
|
|
|
{
|
|
|
|
const struct tcf_proto_ops *ops;
|
|
|
|
bool ret;
|
|
|
|
|
2019-10-08 03:26:28 +07:00
|
|
|
if (strlen(kind) == 0)
|
|
|
|
return false;
|
|
|
|
|
2019-02-11 15:55:48 +07:00
|
|
|
ops = tcf_proto_lookup_ops(kind, false, NULL);
|
|
|
|
/* On error return false to take rtnl lock. Proto lookup/create
|
|
|
|
* functions will perform lookup again and properly handle errors.
|
|
|
|
*/
|
|
|
|
if (IS_ERR(ops))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
ret = !!(ops->flags & TCF_PROTO_OPS_DOIT_UNLOCKED);
|
|
|
|
module_put(ops->owner);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-02-09 20:38:57 +07:00
|
|
|
static struct tcf_proto *tcf_proto_create(const char *kind, u32 protocol,
|
2018-01-18 23:20:50 +07:00
|
|
|
u32 prio, struct tcf_chain *chain,
|
2019-02-11 15:55:45 +07:00
|
|
|
bool rtnl_held,
|
2018-01-18 23:20:50 +07:00
|
|
|
struct netlink_ext_ack *extack)
|
2017-02-09 20:38:57 +07:00
|
|
|
{
|
|
|
|
struct tcf_proto *tp;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
tp = kzalloc(sizeof(*tp), GFP_KERNEL);
|
|
|
|
if (!tp)
|
|
|
|
return ERR_PTR(-ENOBUFS);
|
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
tp->ops = tcf_proto_lookup_ops(kind, rtnl_held, extack);
|
2018-07-23 14:23:04 +07:00
|
|
|
if (IS_ERR(tp->ops)) {
|
|
|
|
err = PTR_ERR(tp->ops);
|
2018-05-11 22:45:32 +07:00
|
|
|
goto errout;
|
2017-02-09 20:38:57 +07:00
|
|
|
}
|
|
|
|
tp->classify = tp->ops->classify;
|
|
|
|
tp->protocol = protocol;
|
|
|
|
tp->prio = prio;
|
2017-05-17 16:08:01 +07:00
|
|
|
tp->chain = chain;
|
2019-02-11 15:55:41 +07:00
|
|
|
spin_lock_init(&tp->lock);
|
2019-02-11 15:55:39 +07:00
|
|
|
refcount_set(&tp->refcnt, 1);
|
2017-02-09 20:38:57 +07:00
|
|
|
|
|
|
|
err = tp->ops->init(tp);
|
|
|
|
if (err) {
|
|
|
|
module_put(tp->ops->owner);
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
return tp;
|
|
|
|
|
|
|
|
errout:
|
|
|
|
kfree(tp);
|
|
|
|
return ERR_PTR(err);
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:39 +07:00
|
|
|
static void tcf_proto_get(struct tcf_proto *tp)
|
|
|
|
{
|
|
|
|
refcount_inc(&tp->refcnt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_chain_put(struct tcf_chain *chain);
|
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
static void tcf_proto_destroy(struct tcf_proto *tp, bool rtnl_held,
|
2019-11-02 21:17:47 +07:00
|
|
|
bool sig_destroy, struct netlink_ext_ack *extack)
|
2017-02-09 20:38:56 +07:00
|
|
|
{
|
2019-02-11 15:55:45 +07:00
|
|
|
tp->ops->destroy(tp, rtnl_held, extack);
|
2019-11-02 21:17:47 +07:00
|
|
|
if (sig_destroy)
|
|
|
|
tcf_proto_signal_destroyed(tp->chain, tp);
|
2019-02-11 15:55:39 +07:00
|
|
|
tcf_chain_put(tp->chain);
|
2017-04-20 04:21:21 +07:00
|
|
|
module_put(tp->ops->owner);
|
|
|
|
kfree_rcu(tp, rcu);
|
2017-02-09 20:38:56 +07:00
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
static void tcf_proto_put(struct tcf_proto *tp, bool rtnl_held,
|
2019-02-11 15:55:39 +07:00
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
if (refcount_dec_and_test(&tp->refcnt))
|
2019-11-02 21:17:47 +07:00
|
|
|
tcf_proto_destroy(tp, rtnl_held, true, extack);
|
2019-02-11 15:55:39 +07:00
|
|
|
}
|
|
|
|
|
net/sched: add delete_empty() to filters and use it in cls_flower
Revert "net/sched: cls_u32: fix refcount leak in the error path of
u32_change()", and fix the u32 refcount leak in a more generic way that
preserves the semantic of rule dumping.
On tc filters that don't support lockless insertion/removal, there is no
need to guard against concurrent insertion when a removal is in progress.
Therefore, for most of them we can avoid a full walk() when deleting, and
just decrease the refcount, like it was done on older Linux kernels.
This fixes situations where walk() was wrongly detecting a non-empty
filter, like it happened with cls_u32 in the error path of change(), thus
leading to failures in the following tdc selftests:
6aa7: (filter, u32) Add/Replace u32 with source match and invalid indev
6658: (filter, u32) Add/Replace u32 with custom hash table and invalid handle
74c2: (filter, u32) Add/Replace u32 filter with invalid hash table id
On cls_flower, and on (future) lockless filters, this check is necessary:
move all the check_empty() logic in a callback so that each filter
can have its own implementation. For cls_flower, it's sufficient to check
if no IDRs have been allocated.
This reverts commit 275c44aa194b7159d1191817b20e076f55f0e620.
Changes since v1:
- document the need for delete_empty() when TCF_PROTO_OPS_DOIT_UNLOCKED
is used, thanks to Vlad Buslov
- implement delete_empty() without doing fl_walk(), thanks to Vlad Buslov
- squash revert and new fix in a single patch, to be nice with bisect
tests that run tdc on u32 filter, thanks to Dave Miller
Fixes: 275c44aa194b ("net/sched: cls_u32: fix refcount leak in the error path of u32_change()")
Fixes: 6676d5e416ee ("net: sched: set dedicated tcf_walker flag when tp is empty")
Suggested-by: Jamal Hadi Salim <jhs@mojatatu.com>
Suggested-by: Vlad Buslov <vladbu@mellanox.com>
Signed-off-by: Davide Caratti <dcaratti@redhat.com>
Reviewed-by: Vlad Buslov <vladbu@mellanox.com>
Tested-by: Jamal Hadi Salim <jhs@mojatatu.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-12-28 22:36:58 +07:00
|
|
|
static bool tcf_proto_check_delete(struct tcf_proto *tp)
|
2019-02-11 15:55:41 +07:00
|
|
|
{
|
net/sched: add delete_empty() to filters and use it in cls_flower
Revert "net/sched: cls_u32: fix refcount leak in the error path of
u32_change()", and fix the u32 refcount leak in a more generic way that
preserves the semantic of rule dumping.
On tc filters that don't support lockless insertion/removal, there is no
need to guard against concurrent insertion when a removal is in progress.
Therefore, for most of them we can avoid a full walk() when deleting, and
just decrease the refcount, like it was done on older Linux kernels.
This fixes situations where walk() was wrongly detecting a non-empty
filter, like it happened with cls_u32 in the error path of change(), thus
leading to failures in the following tdc selftests:
6aa7: (filter, u32) Add/Replace u32 with source match and invalid indev
6658: (filter, u32) Add/Replace u32 with custom hash table and invalid handle
74c2: (filter, u32) Add/Replace u32 filter with invalid hash table id
On cls_flower, and on (future) lockless filters, this check is necessary:
move all the check_empty() logic in a callback so that each filter
can have its own implementation. For cls_flower, it's sufficient to check
if no IDRs have been allocated.
This reverts commit 275c44aa194b7159d1191817b20e076f55f0e620.
Changes since v1:
- document the need for delete_empty() when TCF_PROTO_OPS_DOIT_UNLOCKED
is used, thanks to Vlad Buslov
- implement delete_empty() without doing fl_walk(), thanks to Vlad Buslov
- squash revert and new fix in a single patch, to be nice with bisect
tests that run tdc on u32 filter, thanks to Dave Miller
Fixes: 275c44aa194b ("net/sched: cls_u32: fix refcount leak in the error path of u32_change()")
Fixes: 6676d5e416ee ("net: sched: set dedicated tcf_walker flag when tp is empty")
Suggested-by: Jamal Hadi Salim <jhs@mojatatu.com>
Suggested-by: Vlad Buslov <vladbu@mellanox.com>
Signed-off-by: Davide Caratti <dcaratti@redhat.com>
Reviewed-by: Vlad Buslov <vladbu@mellanox.com>
Tested-by: Jamal Hadi Salim <jhs@mojatatu.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-12-28 22:36:58 +07:00
|
|
|
if (tp->ops->delete_empty)
|
|
|
|
return tp->ops->delete_empty(tp);
|
2019-02-11 15:55:41 +07:00
|
|
|
|
net/sched: add delete_empty() to filters and use it in cls_flower
Revert "net/sched: cls_u32: fix refcount leak in the error path of
u32_change()", and fix the u32 refcount leak in a more generic way that
preserves the semantic of rule dumping.
On tc filters that don't support lockless insertion/removal, there is no
need to guard against concurrent insertion when a removal is in progress.
Therefore, for most of them we can avoid a full walk() when deleting, and
just decrease the refcount, like it was done on older Linux kernels.
This fixes situations where walk() was wrongly detecting a non-empty
filter, like it happened with cls_u32 in the error path of change(), thus
leading to failures in the following tdc selftests:
6aa7: (filter, u32) Add/Replace u32 with source match and invalid indev
6658: (filter, u32) Add/Replace u32 with custom hash table and invalid handle
74c2: (filter, u32) Add/Replace u32 filter with invalid hash table id
On cls_flower, and on (future) lockless filters, this check is necessary:
move all the check_empty() logic in a callback so that each filter
can have its own implementation. For cls_flower, it's sufficient to check
if no IDRs have been allocated.
This reverts commit 275c44aa194b7159d1191817b20e076f55f0e620.
Changes since v1:
- document the need for delete_empty() when TCF_PROTO_OPS_DOIT_UNLOCKED
is used, thanks to Vlad Buslov
- implement delete_empty() without doing fl_walk(), thanks to Vlad Buslov
- squash revert and new fix in a single patch, to be nice with bisect
tests that run tdc on u32 filter, thanks to Dave Miller
Fixes: 275c44aa194b ("net/sched: cls_u32: fix refcount leak in the error path of u32_change()")
Fixes: 6676d5e416ee ("net: sched: set dedicated tcf_walker flag when tp is empty")
Suggested-by: Jamal Hadi Salim <jhs@mojatatu.com>
Suggested-by: Vlad Buslov <vladbu@mellanox.com>
Signed-off-by: Davide Caratti <dcaratti@redhat.com>
Reviewed-by: Vlad Buslov <vladbu@mellanox.com>
Tested-by: Jamal Hadi Salim <jhs@mojatatu.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-12-28 22:36:58 +07:00
|
|
|
tp->deleting = true;
|
2019-02-11 15:55:41 +07:00
|
|
|
return tp->deleting;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_proto_mark_delete(struct tcf_proto *tp)
|
|
|
|
{
|
|
|
|
spin_lock(&tp->lock);
|
|
|
|
tp->deleting = true;
|
|
|
|
spin_unlock(&tp->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool tcf_proto_is_deleting(struct tcf_proto *tp)
|
|
|
|
{
|
|
|
|
bool deleting;
|
|
|
|
|
|
|
|
spin_lock(&tp->lock);
|
|
|
|
deleting = tp->deleting;
|
|
|
|
spin_unlock(&tp->lock);
|
|
|
|
|
|
|
|
return deleting;
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:32 +07:00
|
|
|
#define ASSERT_BLOCK_LOCKED(block) \
|
|
|
|
lockdep_assert_held(&(block)->lock)
|
|
|
|
|
2018-01-17 17:46:45 +07:00
|
|
|
struct tcf_filter_chain_list_item {
|
|
|
|
struct list_head list;
|
|
|
|
tcf_chain_head_change_t *chain_head_change;
|
|
|
|
void *chain_head_change_priv;
|
|
|
|
};
|
|
|
|
|
2017-05-17 16:08:01 +07:00
|
|
|
static struct tcf_chain *tcf_chain_create(struct tcf_block *block,
|
|
|
|
u32 chain_index)
|
2017-05-17 16:07:59 +07:00
|
|
|
{
|
2017-05-17 16:08:01 +07:00
|
|
|
struct tcf_chain *chain;
|
|
|
|
|
2019-02-11 15:55:32 +07:00
|
|
|
ASSERT_BLOCK_LOCKED(block);
|
|
|
|
|
2017-05-17 16:08:01 +07:00
|
|
|
chain = kzalloc(sizeof(*chain), GFP_KERNEL);
|
|
|
|
if (!chain)
|
|
|
|
return NULL;
|
2020-02-16 17:01:23 +07:00
|
|
|
list_add_tail_rcu(&chain->list, &block->chain_list);
|
2019-02-11 15:55:38 +07:00
|
|
|
mutex_init(&chain->filter_chain_lock);
|
2017-05-17 16:08:01 +07:00
|
|
|
chain->block = block;
|
|
|
|
chain->index = chain_index;
|
2017-09-12 06:33:31 +07:00
|
|
|
chain->refcnt = 1;
|
2018-07-23 14:23:05 +07:00
|
|
|
if (!chain->index)
|
|
|
|
block->chain0.chain = chain;
|
2017-05-17 16:08:01 +07:00
|
|
|
return chain;
|
2017-05-17 16:07:59 +07:00
|
|
|
}
|
|
|
|
|
2018-01-17 17:46:45 +07:00
|
|
|
static void tcf_chain_head_change_item(struct tcf_filter_chain_list_item *item,
|
|
|
|
struct tcf_proto *tp_head)
|
|
|
|
{
|
|
|
|
if (item->chain_head_change)
|
|
|
|
item->chain_head_change(tp_head, item->chain_head_change_priv);
|
|
|
|
}
|
2018-07-23 14:23:05 +07:00
|
|
|
|
|
|
|
static void tcf_chain0_head_change(struct tcf_chain *chain,
|
|
|
|
struct tcf_proto *tp_head)
|
2017-11-03 17:46:24 +07:00
|
|
|
{
|
2018-01-17 17:46:45 +07:00
|
|
|
struct tcf_filter_chain_list_item *item;
|
2018-07-23 14:23:05 +07:00
|
|
|
struct tcf_block *block = chain->block;
|
2018-01-17 17:46:45 +07:00
|
|
|
|
2018-07-23 14:23:05 +07:00
|
|
|
if (chain->index)
|
|
|
|
return;
|
2019-02-11 15:55:35 +07:00
|
|
|
|
|
|
|
mutex_lock(&block->lock);
|
2018-07-23 14:23:05 +07:00
|
|
|
list_for_each_entry(item, &block->chain0.filter_chain_list, list)
|
2018-01-17 17:46:45 +07:00
|
|
|
tcf_chain_head_change_item(item, tp_head);
|
2019-02-11 15:55:35 +07:00
|
|
|
mutex_unlock(&block->lock);
|
2017-11-03 17:46:24 +07:00
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:32 +07:00
|
|
|
/* Returns true if block can be safely freed. */
|
|
|
|
|
|
|
|
static bool tcf_chain_detach(struct tcf_chain *chain)
|
2017-05-20 20:01:32 +07:00
|
|
|
{
|
2017-12-05 01:48:18 +07:00
|
|
|
struct tcf_block *block = chain->block;
|
|
|
|
|
2019-02-11 15:55:32 +07:00
|
|
|
ASSERT_BLOCK_LOCKED(block);
|
|
|
|
|
2020-02-16 17:01:23 +07:00
|
|
|
list_del_rcu(&chain->list);
|
2018-07-23 14:23:05 +07:00
|
|
|
if (!chain->index)
|
|
|
|
block->chain0.chain = NULL;
|
2019-02-11 15:55:32 +07:00
|
|
|
|
|
|
|
if (list_empty(&block->chain_list) &&
|
|
|
|
refcount_read(&block->refcnt) == 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_block_destroy(struct tcf_block *block)
|
|
|
|
{
|
|
|
|
mutex_destroy(&block->lock);
|
2019-11-02 21:17:47 +07:00
|
|
|
mutex_destroy(&block->proto_destroy_lock);
|
2019-02-11 15:55:32 +07:00
|
|
|
kfree_rcu(block, rcu);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_chain_destroy(struct tcf_chain *chain, bool free_block)
|
|
|
|
{
|
|
|
|
struct tcf_block *block = chain->block;
|
|
|
|
|
2019-02-11 15:55:38 +07:00
|
|
|
mutex_destroy(&chain->filter_chain_lock);
|
2019-03-20 21:00:16 +07:00
|
|
|
kfree_rcu(chain, rcu);
|
2019-02-11 15:55:32 +07:00
|
|
|
if (free_block)
|
|
|
|
tcf_block_destroy(block);
|
2017-09-12 06:33:31 +07:00
|
|
|
}
|
2017-08-23 03:46:49 +07:00
|
|
|
|
2017-09-12 06:33:31 +07:00
|
|
|
static void tcf_chain_hold(struct tcf_chain *chain)
|
|
|
|
{
|
2019-02-11 15:55:32 +07:00
|
|
|
ASSERT_BLOCK_LOCKED(chain->block);
|
|
|
|
|
2017-09-12 06:33:31 +07:00
|
|
|
++chain->refcnt;
|
2017-05-17 16:07:59 +07:00
|
|
|
}
|
|
|
|
|
2018-08-01 17:36:55 +07:00
|
|
|
static bool tcf_chain_held_by_acts_only(struct tcf_chain *chain)
|
2018-07-27 14:45:05 +07:00
|
|
|
{
|
2019-02-11 15:55:32 +07:00
|
|
|
ASSERT_BLOCK_LOCKED(chain->block);
|
|
|
|
|
2018-07-27 14:45:05 +07:00
|
|
|
/* In case all the references are action references, this
|
2018-08-01 17:36:55 +07:00
|
|
|
* chain should not be shown to the user.
|
2018-07-27 14:45:05 +07:00
|
|
|
*/
|
|
|
|
return chain->refcnt == chain->action_refcnt;
|
|
|
|
}
|
|
|
|
|
2018-07-23 14:23:06 +07:00
|
|
|
static struct tcf_chain *tcf_chain_lookup(struct tcf_block *block,
|
|
|
|
u32 chain_index)
|
2017-05-17 16:08:01 +07:00
|
|
|
{
|
|
|
|
struct tcf_chain *chain;
|
|
|
|
|
2019-02-11 15:55:32 +07:00
|
|
|
ASSERT_BLOCK_LOCKED(block);
|
|
|
|
|
2017-05-17 16:08:01 +07:00
|
|
|
list_for_each_entry(chain, &block->chain_list, list) {
|
2018-07-23 14:23:06 +07:00
|
|
|
if (chain->index == chain_index)
|
2017-09-12 06:33:31 +07:00
|
|
|
return chain;
|
2018-07-23 14:23:06 +07:00
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-02-16 17:01:24 +07:00
|
|
|
#if IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
|
|
|
|
static struct tcf_chain *tcf_chain_lookup_rcu(const struct tcf_block *block,
|
|
|
|
u32 chain_index)
|
|
|
|
{
|
|
|
|
struct tcf_chain *chain;
|
|
|
|
|
|
|
|
list_for_each_entry_rcu(chain, &block->chain_list, list) {
|
|
|
|
if (chain->index == chain_index)
|
|
|
|
return chain;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2018-07-23 14:23:06 +07:00
|
|
|
static int tc_chain_notify(struct tcf_chain *chain, struct sk_buff *oskb,
|
|
|
|
u32 seq, u16 flags, int event, bool unicast);
|
|
|
|
|
2018-08-01 17:36:56 +07:00
|
|
|
static struct tcf_chain *__tcf_chain_get(struct tcf_block *block,
|
|
|
|
u32 chain_index, bool create,
|
|
|
|
bool by_act)
|
2018-07-23 14:23:06 +07:00
|
|
|
{
|
2019-02-11 15:55:32 +07:00
|
|
|
struct tcf_chain *chain = NULL;
|
|
|
|
bool is_first_reference;
|
2018-07-23 14:23:06 +07:00
|
|
|
|
2019-02-11 15:55:32 +07:00
|
|
|
mutex_lock(&block->lock);
|
|
|
|
chain = tcf_chain_lookup(block, chain_index);
|
2018-07-23 14:23:06 +07:00
|
|
|
if (chain) {
|
|
|
|
tcf_chain_hold(chain);
|
2018-08-01 17:36:56 +07:00
|
|
|
} else {
|
|
|
|
if (!create)
|
2019-02-11 15:55:32 +07:00
|
|
|
goto errout;
|
2018-08-01 17:36:56 +07:00
|
|
|
chain = tcf_chain_create(block, chain_index);
|
|
|
|
if (!chain)
|
2019-02-11 15:55:32 +07:00
|
|
|
goto errout;
|
2017-05-17 16:08:01 +07:00
|
|
|
}
|
2017-09-06 18:14:19 +07:00
|
|
|
|
2018-08-01 17:36:56 +07:00
|
|
|
if (by_act)
|
|
|
|
++chain->action_refcnt;
|
2019-02-11 15:55:32 +07:00
|
|
|
is_first_reference = chain->refcnt - chain->action_refcnt == 1;
|
|
|
|
mutex_unlock(&block->lock);
|
2018-08-01 17:36:56 +07:00
|
|
|
|
|
|
|
/* Send notification only in case we got the first
|
|
|
|
* non-action reference. Until then, the chain acts only as
|
|
|
|
* a placeholder for actions pointing to it and user ought
|
|
|
|
* not know about them.
|
|
|
|
*/
|
2019-02-11 15:55:32 +07:00
|
|
|
if (is_first_reference && !by_act)
|
2018-08-01 17:36:56 +07:00
|
|
|
tc_chain_notify(chain, NULL, 0, NLM_F_CREATE | NLM_F_EXCL,
|
|
|
|
RTM_NEWCHAIN, false);
|
|
|
|
|
2018-07-23 14:23:06 +07:00
|
|
|
return chain;
|
2019-02-11 15:55:32 +07:00
|
|
|
|
|
|
|
errout:
|
|
|
|
mutex_unlock(&block->lock);
|
|
|
|
return chain;
|
2017-05-17 16:08:01 +07:00
|
|
|
}
|
2018-08-01 17:36:56 +07:00
|
|
|
|
2018-08-01 17:36:57 +07:00
|
|
|
static struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index,
|
|
|
|
bool create)
|
2018-08-01 17:36:56 +07:00
|
|
|
{
|
|
|
|
return __tcf_chain_get(block, chain_index, create, false);
|
|
|
|
}
|
2017-05-17 16:08:01 +07:00
|
|
|
|
2018-07-27 14:45:05 +07:00
|
|
|
struct tcf_chain *tcf_chain_get_by_act(struct tcf_block *block, u32 chain_index)
|
|
|
|
{
|
2018-08-01 17:36:56 +07:00
|
|
|
return __tcf_chain_get(block, chain_index, true, true);
|
2018-07-27 14:45:05 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tcf_chain_get_by_act);
|
|
|
|
|
2019-02-11 15:55:37 +07:00
|
|
|
static void tc_chain_tmplt_del(const struct tcf_proto_ops *tmplt_ops,
|
|
|
|
void *tmplt_priv);
|
|
|
|
static int tc_chain_notify_delete(const struct tcf_proto_ops *tmplt_ops,
|
|
|
|
void *tmplt_priv, u32 chain_index,
|
|
|
|
struct tcf_block *block, struct sk_buff *oskb,
|
|
|
|
u32 seq, u16 flags, bool unicast);
|
2018-07-23 14:23:07 +07:00
|
|
|
|
2019-02-11 15:55:33 +07:00
|
|
|
static void __tcf_chain_put(struct tcf_chain *chain, bool by_act,
|
|
|
|
bool explicitly_created)
|
2017-05-17 16:08:01 +07:00
|
|
|
{
|
2019-02-11 15:55:32 +07:00
|
|
|
struct tcf_block *block = chain->block;
|
2019-02-11 15:55:37 +07:00
|
|
|
const struct tcf_proto_ops *tmplt_ops;
|
2019-03-06 22:50:43 +07:00
|
|
|
bool free_block = false;
|
2019-02-11 15:55:32 +07:00
|
|
|
unsigned int refcnt;
|
2019-02-11 15:55:37 +07:00
|
|
|
void *tmplt_priv;
|
2019-02-11 15:55:32 +07:00
|
|
|
|
|
|
|
mutex_lock(&block->lock);
|
2019-02-11 15:55:33 +07:00
|
|
|
if (explicitly_created) {
|
|
|
|
if (!chain->explicitly_created) {
|
|
|
|
mutex_unlock(&block->lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
chain->explicitly_created = false;
|
|
|
|
}
|
|
|
|
|
2018-08-01 17:36:56 +07:00
|
|
|
if (by_act)
|
|
|
|
chain->action_refcnt--;
|
2019-02-11 15:55:32 +07:00
|
|
|
|
|
|
|
/* tc_chain_notify_delete can't be called while holding block lock.
|
|
|
|
* However, when block is unlocked chain can be changed concurrently, so
|
|
|
|
* save these to temporary variables.
|
|
|
|
*/
|
|
|
|
refcnt = --chain->refcnt;
|
2019-02-11 15:55:37 +07:00
|
|
|
tmplt_ops = chain->tmplt_ops;
|
|
|
|
tmplt_priv = chain->tmplt_priv;
|
2018-08-01 17:36:56 +07:00
|
|
|
|
|
|
|
/* The last dropped non-action reference will trigger notification. */
|
2019-03-06 22:50:43 +07:00
|
|
|
if (refcnt - chain->action_refcnt == 0 && !by_act) {
|
|
|
|
tc_chain_notify_delete(tmplt_ops, tmplt_priv, chain->index,
|
2019-02-11 15:55:37 +07:00
|
|
|
block, NULL, 0, 0, false);
|
2019-02-11 15:55:42 +07:00
|
|
|
/* Last reference to chain, no need to lock. */
|
|
|
|
chain->flushing = false;
|
|
|
|
}
|
2018-08-01 17:36:56 +07:00
|
|
|
|
2019-03-06 22:50:43 +07:00
|
|
|
if (refcnt == 0)
|
|
|
|
free_block = tcf_chain_detach(chain);
|
|
|
|
mutex_unlock(&block->lock);
|
|
|
|
|
2019-02-11 15:55:32 +07:00
|
|
|
if (refcnt == 0) {
|
2019-02-11 15:55:37 +07:00
|
|
|
tc_chain_tmplt_del(tmplt_ops, tmplt_priv);
|
2019-02-11 15:55:32 +07:00
|
|
|
tcf_chain_destroy(chain, free_block);
|
2018-07-23 14:23:06 +07:00
|
|
|
}
|
2017-05-17 16:08:01 +07:00
|
|
|
}
|
2018-08-01 17:36:56 +07:00
|
|
|
|
2018-08-01 17:36:57 +07:00
|
|
|
static void tcf_chain_put(struct tcf_chain *chain)
|
2018-08-01 17:36:56 +07:00
|
|
|
{
|
2019-02-11 15:55:33 +07:00
|
|
|
__tcf_chain_put(chain, false, false);
|
2018-08-01 17:36:56 +07:00
|
|
|
}
|
2017-05-17 16:08:01 +07:00
|
|
|
|
2018-07-27 14:45:05 +07:00
|
|
|
void tcf_chain_put_by_act(struct tcf_chain *chain)
|
|
|
|
{
|
2019-02-11 15:55:33 +07:00
|
|
|
__tcf_chain_put(chain, true, false);
|
2018-07-27 14:45:05 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tcf_chain_put_by_act);
|
|
|
|
|
2018-07-23 14:23:06 +07:00
|
|
|
static void tcf_chain_put_explicitly_created(struct tcf_chain *chain)
|
|
|
|
{
|
2019-02-11 15:55:33 +07:00
|
|
|
__tcf_chain_put(chain, false, true);
|
2018-07-23 14:23:06 +07:00
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
static void tcf_chain_flush(struct tcf_chain *chain, bool rtnl_held)
|
2018-08-01 17:36:57 +07:00
|
|
|
{
|
2019-02-11 15:55:39 +07:00
|
|
|
struct tcf_proto *tp, *tp_next;
|
2018-08-01 17:36:57 +07:00
|
|
|
|
2019-02-11 15:55:38 +07:00
|
|
|
mutex_lock(&chain->filter_chain_lock);
|
|
|
|
tp = tcf_chain_dereference(chain->filter_chain, chain);
|
2019-11-02 21:17:47 +07:00
|
|
|
while (tp) {
|
|
|
|
tp_next = rcu_dereference_protected(tp->next, 1);
|
|
|
|
tcf_proto_signal_destroying(chain, tp);
|
|
|
|
tp = tp_next;
|
|
|
|
}
|
|
|
|
tp = tcf_chain_dereference(chain->filter_chain, chain);
|
2019-02-11 15:55:39 +07:00
|
|
|
RCU_INIT_POINTER(chain->filter_chain, NULL);
|
2018-08-01 17:36:57 +07:00
|
|
|
tcf_chain0_head_change(chain, NULL);
|
2019-02-11 15:55:42 +07:00
|
|
|
chain->flushing = true;
|
2019-02-11 15:55:38 +07:00
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
|
|
|
|
2018-08-01 17:36:57 +07:00
|
|
|
while (tp) {
|
2019-02-11 15:55:39 +07:00
|
|
|
tp_next = rcu_dereference_protected(tp->next, 1);
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_put(tp, rtnl_held, NULL);
|
2019-02-11 15:55:39 +07:00
|
|
|
tp = tp_next;
|
2018-08-01 17:36:57 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-10 03:55:45 +07:00
|
|
|
static int tcf_block_setup(struct tcf_block *block,
|
|
|
|
struct flow_block_offload *bo);
|
|
|
|
|
2019-12-06 00:03:35 +07:00
|
|
|
static void tc_indr_block_cmd(struct net_device *dev, struct tcf_block *block,
|
|
|
|
flow_indr_block_bind_cb_t *cb, void *cb_priv,
|
|
|
|
enum flow_block_command command, bool ingress)
|
2018-11-10 12:21:26 +07:00
|
|
|
{
|
2019-07-10 03:55:46 +07:00
|
|
|
struct flow_block_offload bo = {
|
2018-11-10 12:21:26 +07:00
|
|
|
.command = command,
|
2019-12-06 00:03:35 +07:00
|
|
|
.binder_type = ingress ?
|
|
|
|
FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS :
|
|
|
|
FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS,
|
2019-08-07 08:13:49 +07:00
|
|
|
.net = dev_net(dev),
|
|
|
|
.block_shared = tcf_block_non_null_shared(block),
|
2018-11-10 12:21:26 +07:00
|
|
|
};
|
2019-07-10 03:55:45 +07:00
|
|
|
INIT_LIST_HEAD(&bo.cb_list);
|
2018-11-10 12:21:26 +07:00
|
|
|
|
2019-08-07 08:13:49 +07:00
|
|
|
if (!block)
|
2018-11-10 12:21:26 +07:00
|
|
|
return;
|
|
|
|
|
2019-08-07 08:13:49 +07:00
|
|
|
bo.block = &block->flow_block;
|
2019-07-19 23:20:16 +07:00
|
|
|
|
2019-08-26 20:44:57 +07:00
|
|
|
down_write(&block->cb_lock);
|
2019-08-07 08:13:49 +07:00
|
|
|
cb(dev, cb_priv, TC_SETUP_BLOCK, &bo);
|
|
|
|
|
|
|
|
tcf_block_setup(block, &bo);
|
2019-08-26 20:44:57 +07:00
|
|
|
up_write(&block->cb_lock);
|
2018-11-10 12:21:26 +07:00
|
|
|
}
|
|
|
|
|
2019-12-06 00:03:35 +07:00
|
|
|
static struct tcf_block *tc_dev_block(struct net_device *dev, bool ingress)
|
2018-11-10 12:21:26 +07:00
|
|
|
{
|
2019-08-07 08:13:52 +07:00
|
|
|
const struct Qdisc_class_ops *cops;
|
2019-12-06 00:03:35 +07:00
|
|
|
const struct Qdisc_ops *ops;
|
2019-08-07 08:13:52 +07:00
|
|
|
struct Qdisc *qdisc;
|
2018-11-10 12:21:26 +07:00
|
|
|
|
2019-08-07 08:13:52 +07:00
|
|
|
if (!dev_ingress_queue(dev))
|
|
|
|
return NULL;
|
2018-11-10 12:21:26 +07:00
|
|
|
|
2019-08-07 08:13:52 +07:00
|
|
|
qdisc = dev_ingress_queue(dev)->qdisc_sleeping;
|
|
|
|
if (!qdisc)
|
|
|
|
return NULL;
|
2018-11-10 12:21:26 +07:00
|
|
|
|
2019-12-06 00:03:35 +07:00
|
|
|
ops = qdisc->ops;
|
|
|
|
if (!ops)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (!ingress && !strcmp("ingress", ops->id))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
cops = ops->cl_ops;
|
2019-08-07 08:13:52 +07:00
|
|
|
if (!cops)
|
|
|
|
return NULL;
|
2018-11-10 12:21:26 +07:00
|
|
|
|
2019-08-07 08:13:52 +07:00
|
|
|
if (!cops->tcf_block)
|
|
|
|
return NULL;
|
2018-11-10 12:21:26 +07:00
|
|
|
|
2019-12-06 00:03:35 +07:00
|
|
|
return cops->tcf_block(qdisc,
|
|
|
|
ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS,
|
|
|
|
NULL);
|
2018-11-10 12:21:26 +07:00
|
|
|
}
|
|
|
|
|
2019-12-06 00:03:35 +07:00
|
|
|
static void tc_indr_block_get_and_cmd(struct net_device *dev,
|
|
|
|
flow_indr_block_bind_cb_t *cb,
|
|
|
|
void *cb_priv,
|
|
|
|
enum flow_block_command command)
|
2019-08-07 08:13:51 +07:00
|
|
|
{
|
2019-12-06 00:03:35 +07:00
|
|
|
struct tcf_block *block;
|
|
|
|
|
|
|
|
block = tc_dev_block(dev, true);
|
|
|
|
tc_indr_block_cmd(dev, block, cb, cb_priv, command, true);
|
2019-08-07 08:13:51 +07:00
|
|
|
|
2019-12-06 00:03:35 +07:00
|
|
|
block = tc_dev_block(dev, false);
|
|
|
|
tc_indr_block_cmd(dev, block, cb, cb_priv, command, false);
|
2019-08-07 08:13:51 +07:00
|
|
|
}
|
|
|
|
|
2019-08-07 08:13:52 +07:00
|
|
|
static void tc_indr_block_call(struct tcf_block *block,
|
|
|
|
struct net_device *dev,
|
2018-11-10 12:21:26 +07:00
|
|
|
struct tcf_block_ext_info *ei,
|
2019-07-10 03:55:40 +07:00
|
|
|
enum flow_block_command command,
|
2018-11-10 12:21:26 +07:00
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
2019-07-10 03:55:46 +07:00
|
|
|
struct flow_block_offload bo = {
|
2018-11-10 12:21:26 +07:00
|
|
|
.command = command,
|
|
|
|
.binder_type = ei->binder_type,
|
2019-07-10 03:55:43 +07:00
|
|
|
.net = dev_net(dev),
|
2019-07-19 23:20:16 +07:00
|
|
|
.block = &block->flow_block,
|
2019-07-10 03:55:46 +07:00
|
|
|
.block_shared = tcf_block_shared(block),
|
2018-11-10 12:21:26 +07:00
|
|
|
.extack = extack,
|
|
|
|
};
|
2019-07-10 03:55:45 +07:00
|
|
|
INIT_LIST_HEAD(&bo.cb_list);
|
2018-11-10 12:21:26 +07:00
|
|
|
|
2020-03-24 06:34:25 +07:00
|
|
|
flow_indr_block_call(dev, &bo, command, TC_SETUP_BLOCK);
|
2019-07-10 03:55:45 +07:00
|
|
|
tcf_block_setup(block, &bo);
|
2018-11-10 12:21:26 +07:00
|
|
|
}
|
|
|
|
|
2018-01-17 17:46:50 +07:00
|
|
|
static bool tcf_block_offload_in_use(struct tcf_block *block)
|
|
|
|
{
|
2019-08-26 20:44:58 +07:00
|
|
|
return atomic_read(&block->offloadcnt);
|
2018-01-17 17:46:50 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int tcf_block_offload_cmd(struct tcf_block *block,
|
|
|
|
struct net_device *dev,
|
|
|
|
struct tcf_block_ext_info *ei,
|
2019-07-10 03:55:40 +07:00
|
|
|
enum flow_block_command command,
|
2018-06-26 04:30:04 +07:00
|
|
|
struct netlink_ext_ack *extack)
|
2017-10-19 20:50:29 +07:00
|
|
|
{
|
2019-07-10 03:55:46 +07:00
|
|
|
struct flow_block_offload bo = {};
|
2019-07-10 03:55:45 +07:00
|
|
|
int err;
|
2017-10-19 20:50:29 +07:00
|
|
|
|
2019-07-10 03:55:43 +07:00
|
|
|
bo.net = dev_net(dev);
|
2017-10-19 20:50:29 +07:00
|
|
|
bo.command = command;
|
|
|
|
bo.binder_type = ei->binder_type;
|
2019-07-19 23:20:16 +07:00
|
|
|
bo.block = &block->flow_block;
|
2019-07-10 03:55:46 +07:00
|
|
|
bo.block_shared = tcf_block_shared(block);
|
2018-06-26 04:30:04 +07:00
|
|
|
bo.extack = extack;
|
2019-07-10 03:55:45 +07:00
|
|
|
INIT_LIST_HEAD(&bo.cb_list);
|
|
|
|
|
|
|
|
err = dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_BLOCK, &bo);
|
2020-04-23 21:57:45 +07:00
|
|
|
if (err < 0) {
|
|
|
|
if (err != -EOPNOTSUPP)
|
|
|
|
NL_SET_ERR_MSG(extack, "Driver ndo_setup_tc failed");
|
2019-07-10 03:55:45 +07:00
|
|
|
return err;
|
2020-04-23 21:57:45 +07:00
|
|
|
}
|
2019-07-10 03:55:45 +07:00
|
|
|
|
|
|
|
return tcf_block_setup(block, &bo);
|
2017-10-19 20:50:29 +07:00
|
|
|
}
|
|
|
|
|
2018-01-17 17:46:50 +07:00
|
|
|
static int tcf_block_offload_bind(struct tcf_block *block, struct Qdisc *q,
|
2018-06-26 04:30:04 +07:00
|
|
|
struct tcf_block_ext_info *ei,
|
|
|
|
struct netlink_ext_ack *extack)
|
2017-10-19 20:50:29 +07:00
|
|
|
{
|
2018-01-17 17:46:50 +07:00
|
|
|
struct net_device *dev = q->dev_queue->dev;
|
|
|
|
int err;
|
|
|
|
|
2019-08-26 20:44:57 +07:00
|
|
|
down_write(&block->cb_lock);
|
2018-01-17 17:46:50 +07:00
|
|
|
if (!dev->netdev_ops->ndo_setup_tc)
|
|
|
|
goto no_offload_dev_inc;
|
|
|
|
|
|
|
|
/* If tc offload feature is disabled and the block we try to bind
|
|
|
|
* to already has some offloaded filters, forbid to bind.
|
|
|
|
*/
|
2018-06-26 04:30:04 +07:00
|
|
|
if (!tc_can_offload(dev) && tcf_block_offload_in_use(block)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Bind to offloaded block failed as dev has offload disabled");
|
2019-08-26 20:44:57 +07:00
|
|
|
err = -EOPNOTSUPP;
|
|
|
|
goto err_unlock;
|
2018-06-26 04:30:04 +07:00
|
|
|
}
|
2018-01-17 17:46:50 +07:00
|
|
|
|
2019-07-10 03:55:40 +07:00
|
|
|
err = tcf_block_offload_cmd(block, dev, ei, FLOW_BLOCK_BIND, extack);
|
2018-01-17 17:46:50 +07:00
|
|
|
if (err == -EOPNOTSUPP)
|
|
|
|
goto no_offload_dev_inc;
|
2018-11-10 12:21:26 +07:00
|
|
|
if (err)
|
2019-08-26 20:44:57 +07:00
|
|
|
goto err_unlock;
|
2018-11-10 12:21:26 +07:00
|
|
|
|
2019-07-10 03:55:40 +07:00
|
|
|
tc_indr_block_call(block, dev, ei, FLOW_BLOCK_BIND, extack);
|
2019-08-26 20:44:57 +07:00
|
|
|
up_write(&block->cb_lock);
|
2018-11-10 12:21:26 +07:00
|
|
|
return 0;
|
2018-01-17 17:46:50 +07:00
|
|
|
|
|
|
|
no_offload_dev_inc:
|
2019-08-26 20:44:57 +07:00
|
|
|
if (tcf_block_offload_in_use(block)) {
|
|
|
|
err = -EOPNOTSUPP;
|
|
|
|
goto err_unlock;
|
|
|
|
}
|
|
|
|
err = 0;
|
2018-01-17 17:46:50 +07:00
|
|
|
block->nooffloaddevcnt++;
|
2019-07-10 03:55:40 +07:00
|
|
|
tc_indr_block_call(block, dev, ei, FLOW_BLOCK_BIND, extack);
|
2019-08-26 20:44:57 +07:00
|
|
|
err_unlock:
|
|
|
|
up_write(&block->cb_lock);
|
|
|
|
return err;
|
2017-10-19 20:50:29 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_block_offload_unbind(struct tcf_block *block, struct Qdisc *q,
|
|
|
|
struct tcf_block_ext_info *ei)
|
|
|
|
{
|
2018-01-17 17:46:50 +07:00
|
|
|
struct net_device *dev = q->dev_queue->dev;
|
|
|
|
int err;
|
|
|
|
|
2019-08-26 20:44:57 +07:00
|
|
|
down_write(&block->cb_lock);
|
2019-07-10 03:55:40 +07:00
|
|
|
tc_indr_block_call(block, dev, ei, FLOW_BLOCK_UNBIND, NULL);
|
2018-11-10 12:21:26 +07:00
|
|
|
|
2018-01-17 17:46:50 +07:00
|
|
|
if (!dev->netdev_ops->ndo_setup_tc)
|
|
|
|
goto no_offload_dev_dec;
|
2019-07-10 03:55:40 +07:00
|
|
|
err = tcf_block_offload_cmd(block, dev, ei, FLOW_BLOCK_UNBIND, NULL);
|
2018-01-17 17:46:50 +07:00
|
|
|
if (err == -EOPNOTSUPP)
|
|
|
|
goto no_offload_dev_dec;
|
2019-08-26 20:44:57 +07:00
|
|
|
up_write(&block->cb_lock);
|
2018-01-17 17:46:50 +07:00
|
|
|
return;
|
|
|
|
|
|
|
|
no_offload_dev_dec:
|
|
|
|
WARN_ON(block->nooffloaddevcnt-- == 0);
|
2019-08-26 20:44:57 +07:00
|
|
|
up_write(&block->cb_lock);
|
2017-10-19 20:50:29 +07:00
|
|
|
}
|
|
|
|
|
2018-01-17 17:46:45 +07:00
|
|
|
static int
|
2018-07-23 14:23:05 +07:00
|
|
|
tcf_chain0_head_change_cb_add(struct tcf_block *block,
|
|
|
|
struct tcf_block_ext_info *ei,
|
|
|
|
struct netlink_ext_ack *extack)
|
2018-01-17 17:46:45 +07:00
|
|
|
{
|
|
|
|
struct tcf_filter_chain_list_item *item;
|
2019-02-11 15:55:35 +07:00
|
|
|
struct tcf_chain *chain0;
|
2018-01-17 17:46:45 +07:00
|
|
|
|
|
|
|
item = kmalloc(sizeof(*item), GFP_KERNEL);
|
|
|
|
if (!item) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Memory allocation for head change callback item failed");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
item->chain_head_change = ei->chain_head_change;
|
|
|
|
item->chain_head_change_priv = ei->chain_head_change_priv;
|
2019-02-11 15:55:35 +07:00
|
|
|
|
|
|
|
mutex_lock(&block->lock);
|
|
|
|
chain0 = block->chain0.chain;
|
2019-02-11 15:55:38 +07:00
|
|
|
if (chain0)
|
|
|
|
tcf_chain_hold(chain0);
|
|
|
|
else
|
|
|
|
list_add(&item->list, &block->chain0.filter_chain_list);
|
2019-02-11 15:55:35 +07:00
|
|
|
mutex_unlock(&block->lock);
|
|
|
|
|
2019-02-11 15:55:38 +07:00
|
|
|
if (chain0) {
|
|
|
|
struct tcf_proto *tp_head;
|
|
|
|
|
|
|
|
mutex_lock(&chain0->filter_chain_lock);
|
|
|
|
|
|
|
|
tp_head = tcf_chain_dereference(chain0->filter_chain, chain0);
|
|
|
|
if (tp_head)
|
|
|
|
tcf_chain_head_change_item(item, tp_head);
|
|
|
|
|
|
|
|
mutex_lock(&block->lock);
|
|
|
|
list_add(&item->list, &block->chain0.filter_chain_list);
|
|
|
|
mutex_unlock(&block->lock);
|
|
|
|
|
|
|
|
mutex_unlock(&chain0->filter_chain_lock);
|
|
|
|
tcf_chain_put(chain0);
|
|
|
|
}
|
|
|
|
|
2018-01-17 17:46:45 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2018-07-23 14:23:05 +07:00
|
|
|
tcf_chain0_head_change_cb_del(struct tcf_block *block,
|
|
|
|
struct tcf_block_ext_info *ei)
|
2018-01-17 17:46:45 +07:00
|
|
|
{
|
|
|
|
struct tcf_filter_chain_list_item *item;
|
|
|
|
|
2019-02-11 15:55:35 +07:00
|
|
|
mutex_lock(&block->lock);
|
2018-07-23 14:23:05 +07:00
|
|
|
list_for_each_entry(item, &block->chain0.filter_chain_list, list) {
|
2018-01-17 17:46:45 +07:00
|
|
|
if ((!ei->chain_head_change && !ei->chain_head_change_priv) ||
|
|
|
|
(item->chain_head_change == ei->chain_head_change &&
|
|
|
|
item->chain_head_change_priv == ei->chain_head_change_priv)) {
|
2019-02-11 15:55:35 +07:00
|
|
|
if (block->chain0.chain)
|
2018-07-23 14:23:05 +07:00
|
|
|
tcf_chain_head_change_item(item, NULL);
|
2018-01-17 17:46:45 +07:00
|
|
|
list_del(&item->list);
|
2019-02-11 15:55:35 +07:00
|
|
|
mutex_unlock(&block->lock);
|
|
|
|
|
2018-01-17 17:46:45 +07:00
|
|
|
kfree(item);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2019-02-11 15:55:35 +07:00
|
|
|
mutex_unlock(&block->lock);
|
2018-01-17 17:46:45 +07:00
|
|
|
WARN_ON(1);
|
|
|
|
}
|
|
|
|
|
2018-01-17 17:46:46 +07:00
|
|
|
struct tcf_net {
|
2018-09-24 23:22:56 +07:00
|
|
|
spinlock_t idr_lock; /* Protects idr */
|
2018-01-17 17:46:46 +07:00
|
|
|
struct idr idr;
|
|
|
|
};
|
|
|
|
|
|
|
|
static unsigned int tcf_net_id;
|
|
|
|
|
|
|
|
static int tcf_block_insert(struct tcf_block *block, struct net *net,
|
2018-02-13 18:00:16 +07:00
|
|
|
struct netlink_ext_ack *extack)
|
2018-01-17 17:46:45 +07:00
|
|
|
{
|
2018-01-17 17:46:46 +07:00
|
|
|
struct tcf_net *tn = net_generic(net, tcf_net_id);
|
2018-09-24 23:22:56 +07:00
|
|
|
int err;
|
|
|
|
|
|
|
|
idr_preload(GFP_KERNEL);
|
|
|
|
spin_lock(&tn->idr_lock);
|
|
|
|
err = idr_alloc_u32(&tn->idr, block, &block->index, block->index,
|
|
|
|
GFP_NOWAIT);
|
|
|
|
spin_unlock(&tn->idr_lock);
|
|
|
|
idr_preload_end();
|
2018-01-17 17:46:46 +07:00
|
|
|
|
2018-09-24 23:22:56 +07:00
|
|
|
return err;
|
2018-01-17 17:46:45 +07:00
|
|
|
}
|
|
|
|
|
2018-01-17 17:46:46 +07:00
|
|
|
static void tcf_block_remove(struct tcf_block *block, struct net *net)
|
|
|
|
{
|
|
|
|
struct tcf_net *tn = net_generic(net, tcf_net_id);
|
|
|
|
|
2018-09-24 23:22:56 +07:00
|
|
|
spin_lock(&tn->idr_lock);
|
2017-11-28 21:48:43 +07:00
|
|
|
idr_remove(&tn->idr, block->index);
|
2018-09-24 23:22:56 +07:00
|
|
|
spin_unlock(&tn->idr_lock);
|
2018-01-17 17:46:46 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct tcf_block *tcf_block_create(struct net *net, struct Qdisc *q,
|
2018-02-13 18:00:16 +07:00
|
|
|
u32 block_index,
|
2018-01-17 17:46:46 +07:00
|
|
|
struct netlink_ext_ack *extack)
|
2017-05-17 16:07:55 +07:00
|
|
|
{
|
2018-01-17 17:46:46 +07:00
|
|
|
struct tcf_block *block;
|
2017-05-17 16:07:55 +07:00
|
|
|
|
2018-01-17 17:46:46 +07:00
|
|
|
block = kzalloc(sizeof(*block), GFP_KERNEL);
|
2017-12-21 00:35:19 +07:00
|
|
|
if (!block) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Memory allocation for block failed");
|
2018-01-17 17:46:46 +07:00
|
|
|
return ERR_PTR(-ENOMEM);
|
2017-12-21 00:35:19 +07:00
|
|
|
}
|
2019-02-11 15:55:32 +07:00
|
|
|
mutex_init(&block->lock);
|
2019-11-02 21:17:47 +07:00
|
|
|
mutex_init(&block->proto_destroy_lock);
|
2019-08-26 20:44:57 +07:00
|
|
|
init_rwsem(&block->cb_lock);
|
2019-07-19 23:20:16 +07:00
|
|
|
flow_block_init(&block->flow_block);
|
2017-05-17 16:08:01 +07:00
|
|
|
INIT_LIST_HEAD(&block->chain_list);
|
2018-01-17 17:46:48 +07:00
|
|
|
INIT_LIST_HEAD(&block->owner_list);
|
2018-07-23 14:23:05 +07:00
|
|
|
INIT_LIST_HEAD(&block->chain0.filter_chain_list);
|
2017-10-19 20:50:31 +07:00
|
|
|
|
2018-09-24 23:22:54 +07:00
|
|
|
refcount_set(&block->refcnt, 1);
|
2018-01-17 17:46:46 +07:00
|
|
|
block->net = net;
|
2018-02-13 18:00:16 +07:00
|
|
|
block->index = block_index;
|
|
|
|
|
|
|
|
/* Don't store q pointer for blocks which are shared */
|
|
|
|
if (!tcf_block_shared(block))
|
|
|
|
block->q = q;
|
2018-01-17 17:46:46 +07:00
|
|
|
return block;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct tcf_block *tcf_block_lookup(struct net *net, u32 block_index)
|
|
|
|
{
|
|
|
|
struct tcf_net *tn = net_generic(net, tcf_net_id);
|
|
|
|
|
2017-11-28 22:01:24 +07:00
|
|
|
return idr_find(&tn->idr, block_index);
|
2018-01-17 17:46:46 +07:00
|
|
|
}
|
|
|
|
|
2018-09-24 23:22:57 +07:00
|
|
|
static struct tcf_block *tcf_block_refcnt_get(struct net *net, u32 block_index)
|
|
|
|
{
|
|
|
|
struct tcf_block *block;
|
|
|
|
|
|
|
|
rcu_read_lock();
|
|
|
|
block = tcf_block_lookup(net, block_index);
|
|
|
|
if (block && !refcount_inc_not_zero(&block->refcnt))
|
|
|
|
block = NULL;
|
|
|
|
rcu_read_unlock();
|
|
|
|
|
|
|
|
return block;
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:36 +07:00
|
|
|
static struct tcf_chain *
|
|
|
|
__tcf_get_next_chain(struct tcf_block *block, struct tcf_chain *chain)
|
2018-09-24 23:22:55 +07:00
|
|
|
{
|
2019-02-11 15:55:36 +07:00
|
|
|
mutex_lock(&block->lock);
|
|
|
|
if (chain)
|
|
|
|
chain = list_is_last(&chain->list, &block->chain_list) ?
|
|
|
|
NULL : list_next_entry(chain, list);
|
|
|
|
else
|
|
|
|
chain = list_first_entry_or_null(&block->chain_list,
|
|
|
|
struct tcf_chain, list);
|
2018-09-24 23:22:55 +07:00
|
|
|
|
2019-02-11 15:55:36 +07:00
|
|
|
/* skip all action-only chains */
|
|
|
|
while (chain && tcf_chain_held_by_acts_only(chain))
|
|
|
|
chain = list_is_last(&chain->list, &block->chain_list) ?
|
|
|
|
NULL : list_next_entry(chain, list);
|
|
|
|
|
|
|
|
if (chain)
|
2018-09-24 23:22:55 +07:00
|
|
|
tcf_chain_hold(chain);
|
2019-02-11 15:55:36 +07:00
|
|
|
mutex_unlock(&block->lock);
|
2018-09-24 23:22:55 +07:00
|
|
|
|
2019-02-11 15:55:36 +07:00
|
|
|
return chain;
|
2018-09-24 23:22:55 +07:00
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:36 +07:00
|
|
|
/* Function to be used by all clients that want to iterate over all chains on
|
|
|
|
* block. It properly obtains block->lock and takes reference to chain before
|
|
|
|
* returning it. Users of this function must be tolerant to concurrent chain
|
|
|
|
* insertion/deletion or ensure that no concurrent chain modification is
|
|
|
|
* possible. Note that all netlink dump callbacks cannot guarantee to provide
|
|
|
|
* consistent dump because rtnl lock is released each time skb is filled with
|
|
|
|
* data and sent to user-space.
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct tcf_chain *
|
|
|
|
tcf_get_next_chain(struct tcf_block *block, struct tcf_chain *chain)
|
2018-09-24 23:22:55 +07:00
|
|
|
{
|
2019-02-11 15:55:36 +07:00
|
|
|
struct tcf_chain *chain_next = __tcf_get_next_chain(block, chain);
|
2018-09-24 23:22:55 +07:00
|
|
|
|
2019-02-11 15:55:36 +07:00
|
|
|
if (chain)
|
2018-09-24 23:22:55 +07:00
|
|
|
tcf_chain_put(chain);
|
2019-02-11 15:55:36 +07:00
|
|
|
|
|
|
|
return chain_next;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tcf_get_next_chain);
|
|
|
|
|
2019-02-11 15:55:40 +07:00
|
|
|
static struct tcf_proto *
|
|
|
|
__tcf_get_next_proto(struct tcf_chain *chain, struct tcf_proto *tp)
|
|
|
|
{
|
2019-02-11 15:55:41 +07:00
|
|
|
u32 prio = 0;
|
|
|
|
|
2019-02-11 15:55:40 +07:00
|
|
|
ASSERT_RTNL();
|
|
|
|
mutex_lock(&chain->filter_chain_lock);
|
|
|
|
|
2019-02-11 15:55:41 +07:00
|
|
|
if (!tp) {
|
2019-02-11 15:55:40 +07:00
|
|
|
tp = tcf_chain_dereference(chain->filter_chain, chain);
|
2019-02-11 15:55:41 +07:00
|
|
|
} else if (tcf_proto_is_deleting(tp)) {
|
|
|
|
/* 'deleting' flag is set and chain->filter_chain_lock was
|
|
|
|
* unlocked, which means next pointer could be invalid. Restart
|
|
|
|
* search.
|
|
|
|
*/
|
|
|
|
prio = tp->prio + 1;
|
|
|
|
tp = tcf_chain_dereference(chain->filter_chain, chain);
|
|
|
|
|
|
|
|
for (; tp; tp = tcf_chain_dereference(tp->next, chain))
|
|
|
|
if (!tp->deleting && tp->prio >= prio)
|
|
|
|
break;
|
|
|
|
} else {
|
2019-02-11 15:55:40 +07:00
|
|
|
tp = tcf_chain_dereference(tp->next, chain);
|
2019-02-11 15:55:41 +07:00
|
|
|
}
|
2019-02-11 15:55:40 +07:00
|
|
|
|
|
|
|
if (tp)
|
|
|
|
tcf_proto_get(tp);
|
|
|
|
|
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
|
|
|
|
|
|
|
return tp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Function to be used by all clients that want to iterate over all tp's on
|
|
|
|
* chain. Users of this function must be tolerant to concurrent tp
|
|
|
|
* insertion/deletion or ensure that no concurrent chain modification is
|
|
|
|
* possible. Note that all netlink dump callbacks cannot guarantee to provide
|
|
|
|
* consistent dump because rtnl lock is released each time skb is filled with
|
|
|
|
* data and sent to user-space.
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct tcf_proto *
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_get_next_proto(struct tcf_chain *chain, struct tcf_proto *tp,
|
|
|
|
bool rtnl_held)
|
2019-02-11 15:55:40 +07:00
|
|
|
{
|
|
|
|
struct tcf_proto *tp_next = __tcf_get_next_proto(chain, tp);
|
|
|
|
|
|
|
|
if (tp)
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_put(tp, rtnl_held, NULL);
|
2019-02-11 15:55:40 +07:00
|
|
|
|
|
|
|
return tp_next;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tcf_get_next_proto);
|
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
static void tcf_block_flush_all_chains(struct tcf_block *block, bool rtnl_held)
|
2019-02-11 15:55:36 +07:00
|
|
|
{
|
|
|
|
struct tcf_chain *chain;
|
|
|
|
|
|
|
|
/* Last reference to block. At this point chains cannot be added or
|
|
|
|
* removed concurrently.
|
|
|
|
*/
|
|
|
|
for (chain = tcf_get_next_chain(block, NULL);
|
|
|
|
chain;
|
|
|
|
chain = tcf_get_next_chain(block, chain)) {
|
|
|
|
tcf_chain_put_explicitly_created(chain);
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_chain_flush(chain, rtnl_held);
|
2018-09-24 23:22:55 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:47 +07:00
|
|
|
/* Lookup Qdisc and increments its reference counter.
|
|
|
|
* Set parent, if necessary.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int __tcf_qdisc_find(struct net *net, struct Qdisc **q,
|
|
|
|
u32 *parent, int ifindex, bool rtnl_held,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
const struct Qdisc_class_ops *cops;
|
|
|
|
struct net_device *dev;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
if (ifindex == TCM_IFINDEX_MAGIC_BLOCK)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
rcu_read_lock();
|
|
|
|
|
|
|
|
/* Find link */
|
|
|
|
dev = dev_get_by_index_rcu(net, ifindex);
|
|
|
|
if (!dev) {
|
|
|
|
rcu_read_unlock();
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Find qdisc */
|
|
|
|
if (!*parent) {
|
|
|
|
*q = dev->qdisc;
|
|
|
|
*parent = (*q)->handle;
|
|
|
|
} else {
|
|
|
|
*q = qdisc_lookup_rcu(dev, TC_H_MAJ(*parent));
|
|
|
|
if (!*q) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Parent Qdisc doesn't exists");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout_rcu;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*q = qdisc_refcount_inc_nz(*q);
|
|
|
|
if (!*q) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Parent Qdisc doesn't exists");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout_rcu;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Is it classful? */
|
|
|
|
cops = (*q)->ops->cl_ops;
|
|
|
|
if (!cops) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Qdisc not classful");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout_qdisc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cops->tcf_block) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Class doesn't support blocks");
|
|
|
|
err = -EOPNOTSUPP;
|
|
|
|
goto errout_qdisc;
|
|
|
|
}
|
|
|
|
|
|
|
|
errout_rcu:
|
|
|
|
/* At this point we know that qdisc is not noop_qdisc,
|
|
|
|
* which means that qdisc holds a reference to net_device
|
|
|
|
* and we hold a reference to qdisc, so it is safe to release
|
|
|
|
* rcu read lock.
|
|
|
|
*/
|
|
|
|
rcu_read_unlock();
|
|
|
|
return err;
|
|
|
|
|
|
|
|
errout_qdisc:
|
|
|
|
rcu_read_unlock();
|
|
|
|
|
|
|
|
if (rtnl_held)
|
|
|
|
qdisc_put(*q);
|
|
|
|
else
|
|
|
|
qdisc_put_unlocked(*q);
|
|
|
|
*q = NULL;
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __tcf_qdisc_cl_find(struct Qdisc *q, u32 parent, unsigned long *cl,
|
|
|
|
int ifindex, struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
if (ifindex == TCM_IFINDEX_MAGIC_BLOCK)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Do we search for filter, attached to class? */
|
|
|
|
if (TC_H_MIN(parent)) {
|
|
|
|
const struct Qdisc_class_ops *cops = q->ops->cl_ops;
|
|
|
|
|
|
|
|
*cl = cops->find(q, parent);
|
|
|
|
if (*cl == 0) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified class doesn't exist");
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct tcf_block *__tcf_block_find(struct net *net, struct Qdisc *q,
|
|
|
|
unsigned long cl, int ifindex,
|
|
|
|
u32 block_index,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
struct tcf_block *block;
|
|
|
|
|
|
|
|
if (ifindex == TCM_IFINDEX_MAGIC_BLOCK) {
|
|
|
|
block = tcf_block_refcnt_get(net, block_index);
|
|
|
|
if (!block) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Block of given index was not found");
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const struct Qdisc_class_ops *cops = q->ops->cl_ops;
|
|
|
|
|
|
|
|
block = cops->tcf_block(q, cl, extack);
|
|
|
|
if (!block)
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
|
|
|
|
if (tcf_block_shared(block)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "This filter block is shared. Please use the block index to manipulate the filters");
|
|
|
|
return ERR_PTR(-EOPNOTSUPP);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Always take reference to block in order to support execution
|
|
|
|
* of rules update path of cls API without rtnl lock. Caller
|
|
|
|
* must release block when it is finished using it. 'if' block
|
|
|
|
* of this conditional obtain reference to block by calling
|
|
|
|
* tcf_block_refcnt_get().
|
|
|
|
*/
|
|
|
|
refcount_inc(&block->refcnt);
|
|
|
|
}
|
|
|
|
|
|
|
|
return block;
|
|
|
|
}
|
|
|
|
|
2018-09-24 23:22:57 +07:00
|
|
|
static void __tcf_block_put(struct tcf_block *block, struct Qdisc *q,
|
2019-02-11 15:55:45 +07:00
|
|
|
struct tcf_block_ext_info *ei, bool rtnl_held)
|
2018-09-24 23:22:57 +07:00
|
|
|
{
|
2019-02-11 15:55:32 +07:00
|
|
|
if (refcount_dec_and_mutex_lock(&block->refcnt, &block->lock)) {
|
2018-09-24 23:22:57 +07:00
|
|
|
/* Flushing/putting all chains will cause the block to be
|
|
|
|
* deallocated when last chain is freed. However, if chain_list
|
|
|
|
* is empty, block has to be manually deallocated. After block
|
|
|
|
* reference counter reached 0, it is no longer possible to
|
|
|
|
* increment it or add new chains to block.
|
|
|
|
*/
|
|
|
|
bool free_block = list_empty(&block->chain_list);
|
|
|
|
|
2019-02-11 15:55:32 +07:00
|
|
|
mutex_unlock(&block->lock);
|
2018-09-24 23:22:57 +07:00
|
|
|
if (tcf_block_shared(block))
|
|
|
|
tcf_block_remove(block, block->net);
|
|
|
|
|
|
|
|
if (q)
|
|
|
|
tcf_block_offload_unbind(block, q, ei);
|
|
|
|
|
|
|
|
if (free_block)
|
2019-02-11 15:55:32 +07:00
|
|
|
tcf_block_destroy(block);
|
2018-09-24 23:22:57 +07:00
|
|
|
else
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_block_flush_all_chains(block, rtnl_held);
|
2018-09-24 23:22:57 +07:00
|
|
|
} else if (q) {
|
|
|
|
tcf_block_offload_unbind(block, q, ei);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
static void tcf_block_refcnt_put(struct tcf_block *block, bool rtnl_held)
|
2018-09-24 23:22:57 +07:00
|
|
|
{
|
2019-02-11 15:55:45 +07:00
|
|
|
__tcf_block_put(block, NULL, NULL, rtnl_held);
|
2018-09-24 23:22:57 +07:00
|
|
|
}
|
|
|
|
|
2018-05-31 13:52:53 +07:00
|
|
|
/* Find tcf block.
|
|
|
|
* Set q, parent, cl when appropriate.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static struct tcf_block *tcf_block_find(struct net *net, struct Qdisc **q,
|
|
|
|
u32 *parent, unsigned long *cl,
|
|
|
|
int ifindex, u32 block_index,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
struct tcf_block *block;
|
2018-09-24 23:22:53 +07:00
|
|
|
int err = 0;
|
2018-05-31 13:52:53 +07:00
|
|
|
|
2019-02-11 15:55:47 +07:00
|
|
|
ASSERT_RTNL();
|
2018-09-24 23:22:53 +07:00
|
|
|
|
2019-02-11 15:55:47 +07:00
|
|
|
err = __tcf_qdisc_find(net, q, parent, ifindex, true, extack);
|
|
|
|
if (err)
|
|
|
|
goto errout;
|
2018-05-31 13:52:53 +07:00
|
|
|
|
2019-02-11 15:55:47 +07:00
|
|
|
err = __tcf_qdisc_cl_find(*q, *parent, cl, ifindex, extack);
|
|
|
|
if (err)
|
|
|
|
goto errout_qdisc;
|
2018-09-24 23:22:58 +07:00
|
|
|
|
2019-02-11 15:55:47 +07:00
|
|
|
block = __tcf_block_find(net, *q, *cl, ifindex, block_index, extack);
|
2019-02-18 16:26:32 +07:00
|
|
|
if (IS_ERR(block)) {
|
|
|
|
err = PTR_ERR(block);
|
2019-02-11 15:55:47 +07:00
|
|
|
goto errout_qdisc;
|
2019-02-18 16:26:32 +07:00
|
|
|
}
|
2018-05-31 13:52:53 +07:00
|
|
|
|
|
|
|
return block;
|
2018-09-24 23:22:53 +07:00
|
|
|
|
|
|
|
errout_qdisc:
|
2019-02-11 15:55:47 +07:00
|
|
|
if (*q)
|
2018-09-24 23:22:53 +07:00
|
|
|
qdisc_put(*q);
|
2019-02-11 15:55:47 +07:00
|
|
|
errout:
|
|
|
|
*q = NULL;
|
2018-09-24 23:22:53 +07:00
|
|
|
return ERR_PTR(err);
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
static void tcf_block_release(struct Qdisc *q, struct tcf_block *block,
|
|
|
|
bool rtnl_held)
|
2018-09-24 23:22:53 +07:00
|
|
|
{
|
2018-09-24 23:22:58 +07:00
|
|
|
if (!IS_ERR_OR_NULL(block))
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_block_refcnt_put(block, rtnl_held);
|
2018-09-24 23:22:58 +07:00
|
|
|
|
2019-02-11 15:55:48 +07:00
|
|
|
if (q) {
|
|
|
|
if (rtnl_held)
|
|
|
|
qdisc_put(q);
|
|
|
|
else
|
|
|
|
qdisc_put_unlocked(q);
|
|
|
|
}
|
2018-05-31 13:52:53 +07:00
|
|
|
}
|
|
|
|
|
2018-01-17 17:46:48 +07:00
|
|
|
struct tcf_block_owner_item {
|
|
|
|
struct list_head list;
|
|
|
|
struct Qdisc *q;
|
2019-07-10 03:55:41 +07:00
|
|
|
enum flow_block_binder_type binder_type;
|
2018-01-17 17:46:48 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
|
|
tcf_block_owner_netif_keep_dst(struct tcf_block *block,
|
|
|
|
struct Qdisc *q,
|
2019-07-10 03:55:41 +07:00
|
|
|
enum flow_block_binder_type binder_type)
|
2018-01-17 17:46:48 +07:00
|
|
|
{
|
|
|
|
if (block->keep_dst &&
|
2019-07-10 03:55:41 +07:00
|
|
|
binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS &&
|
|
|
|
binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS)
|
2018-01-17 17:46:48 +07:00
|
|
|
netif_keep_dst(qdisc_dev(q));
|
|
|
|
}
|
|
|
|
|
|
|
|
void tcf_block_netif_keep_dst(struct tcf_block *block)
|
|
|
|
{
|
|
|
|
struct tcf_block_owner_item *item;
|
|
|
|
|
|
|
|
block->keep_dst = true;
|
|
|
|
list_for_each_entry(item, &block->owner_list, list)
|
|
|
|
tcf_block_owner_netif_keep_dst(block, item->q,
|
|
|
|
item->binder_type);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tcf_block_netif_keep_dst);
|
|
|
|
|
|
|
|
static int tcf_block_owner_add(struct tcf_block *block,
|
|
|
|
struct Qdisc *q,
|
2019-07-10 03:55:41 +07:00
|
|
|
enum flow_block_binder_type binder_type)
|
2018-01-17 17:46:48 +07:00
|
|
|
{
|
|
|
|
struct tcf_block_owner_item *item;
|
|
|
|
|
|
|
|
item = kmalloc(sizeof(*item), GFP_KERNEL);
|
|
|
|
if (!item)
|
|
|
|
return -ENOMEM;
|
|
|
|
item->q = q;
|
|
|
|
item->binder_type = binder_type;
|
|
|
|
list_add(&item->list, &block->owner_list);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_block_owner_del(struct tcf_block *block,
|
|
|
|
struct Qdisc *q,
|
2019-07-10 03:55:41 +07:00
|
|
|
enum flow_block_binder_type binder_type)
|
2018-01-17 17:46:48 +07:00
|
|
|
{
|
|
|
|
struct tcf_block_owner_item *item;
|
|
|
|
|
|
|
|
list_for_each_entry(item, &block->owner_list, list) {
|
|
|
|
if (item->q == q && item->binder_type == binder_type) {
|
|
|
|
list_del(&item->list);
|
|
|
|
kfree(item);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
WARN_ON(1);
|
|
|
|
}
|
|
|
|
|
2018-01-17 17:46:46 +07:00
|
|
|
int tcf_block_get_ext(struct tcf_block **p_block, struct Qdisc *q,
|
|
|
|
struct tcf_block_ext_info *ei,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
struct net *net = qdisc_net(q);
|
|
|
|
struct tcf_block *block = NULL;
|
|
|
|
int err;
|
|
|
|
|
2018-09-24 23:22:58 +07:00
|
|
|
if (ei->block_index)
|
2018-01-17 17:46:46 +07:00
|
|
|
/* block_index not 0 means the shared block is requested */
|
2018-09-24 23:22:58 +07:00
|
|
|
block = tcf_block_refcnt_get(net, ei->block_index);
|
2018-01-17 17:46:46 +07:00
|
|
|
|
|
|
|
if (!block) {
|
2018-02-13 18:00:16 +07:00
|
|
|
block = tcf_block_create(net, q, ei->block_index, extack);
|
2018-01-17 17:46:46 +07:00
|
|
|
if (IS_ERR(block))
|
|
|
|
return PTR_ERR(block);
|
2018-02-13 18:00:16 +07:00
|
|
|
if (tcf_block_shared(block)) {
|
|
|
|
err = tcf_block_insert(block, net, extack);
|
2018-01-17 17:46:46 +07:00
|
|
|
if (err)
|
|
|
|
goto err_block_insert;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-17 17:46:48 +07:00
|
|
|
err = tcf_block_owner_add(block, q, ei->binder_type);
|
|
|
|
if (err)
|
|
|
|
goto err_block_owner_add;
|
|
|
|
|
|
|
|
tcf_block_owner_netif_keep_dst(block, q, ei->binder_type);
|
|
|
|
|
2018-07-23 14:23:05 +07:00
|
|
|
err = tcf_chain0_head_change_cb_add(block, ei, extack);
|
2018-01-17 17:46:45 +07:00
|
|
|
if (err)
|
2018-07-23 14:23:05 +07:00
|
|
|
goto err_chain0_head_change_cb_add;
|
2018-01-17 17:46:50 +07:00
|
|
|
|
2018-06-26 04:30:04 +07:00
|
|
|
err = tcf_block_offload_bind(block, q, ei, extack);
|
2018-01-17 17:46:50 +07:00
|
|
|
if (err)
|
|
|
|
goto err_block_offload_bind;
|
|
|
|
|
2017-05-17 16:07:55 +07:00
|
|
|
*p_block = block;
|
|
|
|
return 0;
|
2017-05-17 16:07:59 +07:00
|
|
|
|
2018-01-17 17:46:50 +07:00
|
|
|
err_block_offload_bind:
|
2018-07-23 14:23:05 +07:00
|
|
|
tcf_chain0_head_change_cb_del(block, ei);
|
|
|
|
err_chain0_head_change_cb_add:
|
2018-01-17 17:46:48 +07:00
|
|
|
tcf_block_owner_del(block, q, ei->binder_type);
|
|
|
|
err_block_owner_add:
|
2018-01-17 17:46:46 +07:00
|
|
|
err_block_insert:
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_block_refcnt_put(block, true);
|
2017-05-17 16:07:59 +07:00
|
|
|
return err;
|
2017-05-17 16:07:55 +07:00
|
|
|
}
|
2017-10-19 20:50:29 +07:00
|
|
|
EXPORT_SYMBOL(tcf_block_get_ext);
|
|
|
|
|
2017-11-03 17:46:24 +07:00
|
|
|
static void tcf_chain_head_change_dflt(struct tcf_proto *tp_head, void *priv)
|
|
|
|
{
|
|
|
|
struct tcf_proto __rcu **p_filter_chain = priv;
|
|
|
|
|
|
|
|
rcu_assign_pointer(*p_filter_chain, tp_head);
|
|
|
|
}
|
|
|
|
|
2017-10-19 20:50:29 +07:00
|
|
|
int tcf_block_get(struct tcf_block **p_block,
|
2017-12-21 00:35:19 +07:00
|
|
|
struct tcf_proto __rcu **p_filter_chain, struct Qdisc *q,
|
|
|
|
struct netlink_ext_ack *extack)
|
2017-10-19 20:50:29 +07:00
|
|
|
{
|
2017-11-03 17:46:24 +07:00
|
|
|
struct tcf_block_ext_info ei = {
|
|
|
|
.chain_head_change = tcf_chain_head_change_dflt,
|
|
|
|
.chain_head_change_priv = p_filter_chain,
|
|
|
|
};
|
2017-10-19 20:50:29 +07:00
|
|
|
|
2017-11-03 17:46:24 +07:00
|
|
|
WARN_ON(!p_filter_chain);
|
2017-12-21 00:35:19 +07:00
|
|
|
return tcf_block_get_ext(p_block, q, &ei, extack);
|
2017-10-19 20:50:29 +07:00
|
|
|
}
|
2017-05-17 16:07:55 +07:00
|
|
|
EXPORT_SYMBOL(tcf_block_get);
|
|
|
|
|
2017-10-27 08:24:28 +07:00
|
|
|
/* XXX: Standalone actions are not allowed to jump to any chain, and bound
|
2017-11-24 18:27:58 +07:00
|
|
|
* actions should be all removed after flushing.
|
2017-10-27 08:24:28 +07:00
|
|
|
*/
|
2017-11-03 17:46:24 +07:00
|
|
|
void tcf_block_put_ext(struct tcf_block *block, struct Qdisc *q,
|
2017-10-30 12:10:01 +07:00
|
|
|
struct tcf_block_ext_info *ei)
|
2017-10-27 08:24:28 +07:00
|
|
|
{
|
2017-12-17 10:11:55 +07:00
|
|
|
if (!block)
|
|
|
|
return;
|
2018-07-23 14:23:05 +07:00
|
|
|
tcf_chain0_head_change_cb_del(block, ei);
|
2018-01-17 17:46:48 +07:00
|
|
|
tcf_block_owner_del(block, q, ei->binder_type);
|
2017-11-24 18:27:58 +07:00
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
__tcf_block_put(block, q, ei, true);
|
2017-05-17 16:07:55 +07:00
|
|
|
}
|
2017-10-19 20:50:29 +07:00
|
|
|
EXPORT_SYMBOL(tcf_block_put_ext);
|
|
|
|
|
|
|
|
void tcf_block_put(struct tcf_block *block)
|
|
|
|
{
|
|
|
|
struct tcf_block_ext_info ei = {0, };
|
|
|
|
|
2017-12-21 19:13:59 +07:00
|
|
|
if (!block)
|
|
|
|
return;
|
2017-11-03 17:46:24 +07:00
|
|
|
tcf_block_put_ext(block, block->q, &ei);
|
2017-10-19 20:50:29 +07:00
|
|
|
}
|
2017-10-30 12:10:01 +07:00
|
|
|
|
2017-05-17 16:07:55 +07:00
|
|
|
EXPORT_SYMBOL(tcf_block_put);
|
2017-02-09 20:38:56 +07:00
|
|
|
|
2018-06-26 04:30:10 +07:00
|
|
|
static int
|
2019-07-19 23:20:15 +07:00
|
|
|
tcf_block_playback_offloads(struct tcf_block *block, flow_setup_cb_t *cb,
|
2018-06-26 04:30:10 +07:00
|
|
|
void *cb_priv, bool add, bool offload_in_use,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
2019-02-11 15:55:36 +07:00
|
|
|
struct tcf_chain *chain, *chain_prev;
|
2019-02-11 15:55:40 +07:00
|
|
|
struct tcf_proto *tp, *tp_prev;
|
2018-06-26 04:30:10 +07:00
|
|
|
int err;
|
|
|
|
|
2019-08-26 20:44:57 +07:00
|
|
|
lockdep_assert_held(&block->cb_lock);
|
|
|
|
|
2019-02-11 15:55:36 +07:00
|
|
|
for (chain = __tcf_get_next_chain(block, NULL);
|
|
|
|
chain;
|
|
|
|
chain_prev = chain,
|
|
|
|
chain = __tcf_get_next_chain(block, chain),
|
|
|
|
tcf_chain_put(chain_prev)) {
|
2019-02-11 15:55:40 +07:00
|
|
|
for (tp = __tcf_get_next_proto(chain, NULL); tp;
|
|
|
|
tp_prev = tp,
|
|
|
|
tp = __tcf_get_next_proto(chain, tp),
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_put(tp_prev, true, NULL)) {
|
2018-06-26 04:30:10 +07:00
|
|
|
if (tp->ops->reoffload) {
|
|
|
|
err = tp->ops->reoffload(tp, add, cb, cb_priv,
|
|
|
|
extack);
|
|
|
|
if (err && add)
|
|
|
|
goto err_playback_remove;
|
|
|
|
} else if (add && offload_in_use) {
|
|
|
|
err = -EOPNOTSUPP;
|
|
|
|
NL_SET_ERR_MSG(extack, "Filter HW offload failed - classifier without re-offloading support");
|
|
|
|
goto err_playback_remove;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_playback_remove:
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_put(tp, true, NULL);
|
2019-02-11 15:55:36 +07:00
|
|
|
tcf_chain_put(chain);
|
2018-06-26 04:30:10 +07:00
|
|
|
tcf_block_playback_offloads(block, cb, cb_priv, false, offload_in_use,
|
|
|
|
extack);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2019-07-10 03:55:45 +07:00
|
|
|
static int tcf_block_bind(struct tcf_block *block,
|
|
|
|
struct flow_block_offload *bo)
|
|
|
|
{
|
|
|
|
struct flow_block_cb *block_cb, *next;
|
|
|
|
int err, i = 0;
|
|
|
|
|
2019-08-26 20:44:57 +07:00
|
|
|
lockdep_assert_held(&block->cb_lock);
|
|
|
|
|
2019-07-10 03:55:45 +07:00
|
|
|
list_for_each_entry(block_cb, &bo->cb_list, list) {
|
|
|
|
err = tcf_block_playback_offloads(block, block_cb->cb,
|
|
|
|
block_cb->cb_priv, true,
|
|
|
|
tcf_block_offload_in_use(block),
|
|
|
|
bo->extack);
|
|
|
|
if (err)
|
|
|
|
goto err_unroll;
|
2019-08-26 20:45:01 +07:00
|
|
|
if (!bo->unlocked_driver_cb)
|
|
|
|
block->lockeddevcnt++;
|
2019-07-10 03:55:45 +07:00
|
|
|
|
|
|
|
i++;
|
|
|
|
}
|
2019-07-19 23:20:16 +07:00
|
|
|
list_splice(&bo->cb_list, &block->flow_block.cb_list);
|
2019-07-10 03:55:45 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_unroll:
|
|
|
|
list_for_each_entry_safe(block_cb, next, &bo->cb_list, list) {
|
|
|
|
if (i-- > 0) {
|
|
|
|
list_del(&block_cb->list);
|
|
|
|
tcf_block_playback_offloads(block, block_cb->cb,
|
|
|
|
block_cb->cb_priv, false,
|
|
|
|
tcf_block_offload_in_use(block),
|
|
|
|
NULL);
|
2019-08-26 20:45:01 +07:00
|
|
|
if (!bo->unlocked_driver_cb)
|
|
|
|
block->lockeddevcnt--;
|
2019-07-10 03:55:45 +07:00
|
|
|
}
|
|
|
|
flow_block_cb_free(block_cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_block_unbind(struct tcf_block *block,
|
|
|
|
struct flow_block_offload *bo)
|
|
|
|
{
|
|
|
|
struct flow_block_cb *block_cb, *next;
|
|
|
|
|
2019-08-26 20:44:57 +07:00
|
|
|
lockdep_assert_held(&block->cb_lock);
|
|
|
|
|
2019-07-10 03:55:45 +07:00
|
|
|
list_for_each_entry_safe(block_cb, next, &bo->cb_list, list) {
|
|
|
|
tcf_block_playback_offloads(block, block_cb->cb,
|
|
|
|
block_cb->cb_priv, false,
|
|
|
|
tcf_block_offload_in_use(block),
|
|
|
|
NULL);
|
|
|
|
list_del(&block_cb->list);
|
|
|
|
flow_block_cb_free(block_cb);
|
2019-08-26 20:45:01 +07:00
|
|
|
if (!bo->unlocked_driver_cb)
|
|
|
|
block->lockeddevcnt--;
|
2019-07-10 03:55:45 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int tcf_block_setup(struct tcf_block *block,
|
|
|
|
struct flow_block_offload *bo)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
switch (bo->command) {
|
|
|
|
case FLOW_BLOCK_BIND:
|
|
|
|
err = tcf_block_bind(block, bo);
|
|
|
|
break;
|
|
|
|
case FLOW_BLOCK_UNBIND:
|
|
|
|
err = 0;
|
|
|
|
tcf_block_unbind(block, bo);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
err = -EOPNOTSUPP;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2017-05-17 16:07:54 +07:00
|
|
|
/* Main classifier routine: scans classifier chain attached
|
|
|
|
* to this qdisc, (optionally) tests for protocol and asks
|
|
|
|
* specific classifiers.
|
|
|
|
*/
|
2020-02-16 17:01:21 +07:00
|
|
|
static inline int __tcf_classify(struct sk_buff *skb,
|
|
|
|
const struct tcf_proto *tp,
|
2020-02-16 17:01:24 +07:00
|
|
|
const struct tcf_proto *orig_tp,
|
2020-02-16 17:01:21 +07:00
|
|
|
struct tcf_result *res,
|
|
|
|
bool compat_mode,
|
|
|
|
u32 *last_executed_chain)
|
2017-05-17 16:07:54 +07:00
|
|
|
{
|
|
|
|
#ifdef CONFIG_NET_CLS_ACT
|
|
|
|
const int max_reclassify_loop = 4;
|
2017-05-23 14:11:59 +07:00
|
|
|
const struct tcf_proto *first_tp;
|
2017-05-17 16:07:54 +07:00
|
|
|
int limit = 0;
|
|
|
|
|
|
|
|
reclassify:
|
|
|
|
#endif
|
|
|
|
for (; tp; tp = rcu_dereference_bh(tp->next)) {
|
net_sched: refetch skb protocol for each filter
Martin reported a set of filters don't work after changing
from reclassify to continue. Looking into the code, it
looks like skb protocol is not always fetched for each
iteration of the filters. But, as demonstrated by Martin,
TC actions could modify skb->protocol, for example act_vlan,
this means we have to refetch skb protocol in each iteration,
rather than using the one we fetch in the beginning of the loop.
This bug is _not_ introduced by commit 3b3ae880266d
("net: sched: consolidate tc_classify{,_compat}"), technically,
if act_vlan is the only action that modifies skb protocol, then
it is commit c7e2b9689ef8 ("sched: introduce vlan action") which
introduced this bug.
Reported-by: Martin Olsson <martin.olsson+netdev@sentorsecurity.com>
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Cc: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-01-12 09:55:42 +07:00
|
|
|
__be16 protocol = tc_skb_protocol(skb);
|
2017-05-17 16:07:54 +07:00
|
|
|
int err;
|
|
|
|
|
|
|
|
if (tp->protocol != protocol &&
|
|
|
|
tp->protocol != htons(ETH_P_ALL))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
err = tp->classify(skb, tp, res);
|
|
|
|
#ifdef CONFIG_NET_CLS_ACT
|
2017-05-17 16:08:03 +07:00
|
|
|
if (unlikely(err == TC_ACT_RECLASSIFY && !compat_mode)) {
|
2017-05-23 14:11:59 +07:00
|
|
|
first_tp = orig_tp;
|
2020-02-16 17:01:21 +07:00
|
|
|
*last_executed_chain = first_tp->chain->index;
|
2017-05-17 16:07:54 +07:00
|
|
|
goto reset;
|
2017-05-17 16:08:03 +07:00
|
|
|
} else if (unlikely(TC_ACT_EXT_CMP(err, TC_ACT_GOTO_CHAIN))) {
|
2017-05-23 14:11:59 +07:00
|
|
|
first_tp = res->goto_tp;
|
2020-02-16 17:01:21 +07:00
|
|
|
*last_executed_chain = err & TC_ACT_EXT_VAL_MASK;
|
2017-05-17 16:08:03 +07:00
|
|
|
goto reset;
|
|
|
|
}
|
2017-05-17 16:07:54 +07:00
|
|
|
#endif
|
|
|
|
if (err >= 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TC_ACT_UNSPEC; /* signal: continue lookup */
|
|
|
|
#ifdef CONFIG_NET_CLS_ACT
|
|
|
|
reset:
|
|
|
|
if (unlikely(limit++ >= max_reclassify_loop)) {
|
2018-01-17 17:46:47 +07:00
|
|
|
net_notice_ratelimited("%u: reclassify loop, rule prio %u, protocol %02x\n",
|
|
|
|
tp->chain->block->index,
|
|
|
|
tp->prio & 0xffff,
|
2017-05-17 16:07:54 +07:00
|
|
|
ntohs(tp->protocol));
|
|
|
|
return TC_ACT_SHOT;
|
|
|
|
}
|
|
|
|
|
2017-05-23 14:11:59 +07:00
|
|
|
tp = first_tp;
|
2017-05-17 16:07:54 +07:00
|
|
|
goto reclassify;
|
|
|
|
#endif
|
|
|
|
}
|
2020-02-16 17:01:21 +07:00
|
|
|
|
|
|
|
int tcf_classify(struct sk_buff *skb, const struct tcf_proto *tp,
|
|
|
|
struct tcf_result *res, bool compat_mode)
|
|
|
|
{
|
|
|
|
u32 last_executed_chain = 0;
|
|
|
|
|
2020-02-16 17:01:24 +07:00
|
|
|
return __tcf_classify(skb, tp, tp, res, compat_mode,
|
2020-02-16 17:01:21 +07:00
|
|
|
&last_executed_chain);
|
|
|
|
}
|
2017-05-17 16:07:54 +07:00
|
|
|
EXPORT_SYMBOL(tcf_classify);
|
|
|
|
|
2020-02-16 17:01:22 +07:00
|
|
|
int tcf_classify_ingress(struct sk_buff *skb,
|
|
|
|
const struct tcf_block *ingress_block,
|
|
|
|
const struct tcf_proto *tp,
|
2020-02-16 17:01:21 +07:00
|
|
|
struct tcf_result *res, bool compat_mode)
|
|
|
|
{
|
|
|
|
#if !IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
|
|
|
|
u32 last_executed_chain = 0;
|
|
|
|
|
2020-02-16 17:01:24 +07:00
|
|
|
return __tcf_classify(skb, tp, tp, res, compat_mode,
|
2020-02-16 17:01:21 +07:00
|
|
|
&last_executed_chain);
|
|
|
|
#else
|
|
|
|
u32 last_executed_chain = tp ? tp->chain->index : 0;
|
2020-02-16 17:01:24 +07:00
|
|
|
const struct tcf_proto *orig_tp = tp;
|
2020-02-16 17:01:21 +07:00
|
|
|
struct tc_skb_ext *ext;
|
|
|
|
int ret;
|
|
|
|
|
2020-02-16 17:01:24 +07:00
|
|
|
ext = skb_ext_find(skb, TC_SKB_EXT);
|
|
|
|
|
|
|
|
if (ext && ext->chain) {
|
|
|
|
struct tcf_chain *fchain;
|
|
|
|
|
|
|
|
fchain = tcf_chain_lookup_rcu(ingress_block, ext->chain);
|
|
|
|
if (!fchain)
|
|
|
|
return TC_ACT_SHOT;
|
|
|
|
|
|
|
|
/* Consume, so cloned/redirect skbs won't inherit ext */
|
|
|
|
skb_ext_del(skb, TC_SKB_EXT);
|
|
|
|
|
|
|
|
tp = rcu_dereference_bh(fchain->filter_chain);
|
2020-04-06 22:36:56 +07:00
|
|
|
last_executed_chain = fchain->index;
|
2020-02-16 17:01:24 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = __tcf_classify(skb, tp, orig_tp, res, compat_mode,
|
|
|
|
&last_executed_chain);
|
2020-02-16 17:01:21 +07:00
|
|
|
|
|
|
|
/* If we missed on some chain */
|
|
|
|
if (ret == TC_ACT_UNSPEC && last_executed_chain) {
|
|
|
|
ext = skb_ext_add(skb, TC_SKB_EXT);
|
|
|
|
if (WARN_ON_ONCE(!ext))
|
|
|
|
return TC_ACT_SHOT;
|
|
|
|
ext->chain = last_executed_chain;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tcf_classify_ingress);
|
|
|
|
|
2017-05-17 16:07:59 +07:00
|
|
|
struct tcf_chain_info {
|
|
|
|
struct tcf_proto __rcu **pprev;
|
|
|
|
struct tcf_proto __rcu *next;
|
|
|
|
};
|
|
|
|
|
2019-02-11 15:55:38 +07:00
|
|
|
static struct tcf_proto *tcf_chain_tp_prev(struct tcf_chain *chain,
|
|
|
|
struct tcf_chain_info *chain_info)
|
2017-05-17 16:07:59 +07:00
|
|
|
{
|
2019-02-11 15:55:38 +07:00
|
|
|
return tcf_chain_dereference(*chain_info->pprev, chain);
|
2017-05-17 16:07:59 +07:00
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:42 +07:00
|
|
|
static int tcf_chain_tp_insert(struct tcf_chain *chain,
|
|
|
|
struct tcf_chain_info *chain_info,
|
|
|
|
struct tcf_proto *tp)
|
2017-05-17 16:07:59 +07:00
|
|
|
{
|
2019-02-11 15:55:42 +07:00
|
|
|
if (chain->flushing)
|
|
|
|
return -EAGAIN;
|
|
|
|
|
2017-11-03 17:46:24 +07:00
|
|
|
if (*chain_info->pprev == chain->filter_chain)
|
2018-07-23 14:23:05 +07:00
|
|
|
tcf_chain0_head_change(chain, tp);
|
2019-02-11 15:55:39 +07:00
|
|
|
tcf_proto_get(tp);
|
2019-02-11 15:55:38 +07:00
|
|
|
RCU_INIT_POINTER(tp->next, tcf_chain_tp_prev(chain, chain_info));
|
2017-05-17 16:07:59 +07:00
|
|
|
rcu_assign_pointer(*chain_info->pprev, tp);
|
2019-02-11 15:55:42 +07:00
|
|
|
|
|
|
|
return 0;
|
2017-05-17 16:07:59 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_chain_tp_remove(struct tcf_chain *chain,
|
|
|
|
struct tcf_chain_info *chain_info,
|
|
|
|
struct tcf_proto *tp)
|
|
|
|
{
|
2019-02-11 15:55:38 +07:00
|
|
|
struct tcf_proto *next = tcf_chain_dereference(chain_info->next, chain);
|
2017-05-17 16:07:59 +07:00
|
|
|
|
2019-02-11 15:55:41 +07:00
|
|
|
tcf_proto_mark_delete(tp);
|
2017-11-03 17:46:24 +07:00
|
|
|
if (tp == chain->filter_chain)
|
2018-07-23 14:23:05 +07:00
|
|
|
tcf_chain0_head_change(chain, next);
|
2017-05-17 16:07:59 +07:00
|
|
|
RCU_INIT_POINTER(*chain_info->pprev, next);
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:41 +07:00
|
|
|
static struct tcf_proto *tcf_chain_tp_find(struct tcf_chain *chain,
|
|
|
|
struct tcf_chain_info *chain_info,
|
|
|
|
u32 protocol, u32 prio,
|
|
|
|
bool prio_allocate);
|
|
|
|
|
|
|
|
/* Try to insert new proto.
|
|
|
|
* If proto with specified priority already exists, free new proto
|
|
|
|
* and return existing one.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static struct tcf_proto *tcf_chain_tp_insert_unique(struct tcf_chain *chain,
|
|
|
|
struct tcf_proto *tp_new,
|
2019-02-11 15:55:45 +07:00
|
|
|
u32 protocol, u32 prio,
|
|
|
|
bool rtnl_held)
|
2019-02-11 15:55:41 +07:00
|
|
|
{
|
|
|
|
struct tcf_chain_info chain_info;
|
|
|
|
struct tcf_proto *tp;
|
2019-02-11 15:55:42 +07:00
|
|
|
int err = 0;
|
2019-02-11 15:55:41 +07:00
|
|
|
|
|
|
|
mutex_lock(&chain->filter_chain_lock);
|
|
|
|
|
2019-11-02 21:17:47 +07:00
|
|
|
if (tcf_proto_exists_destroying(chain, tp_new)) {
|
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
|
|
|
tcf_proto_destroy(tp_new, rtnl_held, false, NULL);
|
|
|
|
return ERR_PTR(-EAGAIN);
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:41 +07:00
|
|
|
tp = tcf_chain_tp_find(chain, &chain_info,
|
|
|
|
protocol, prio, false);
|
|
|
|
if (!tp)
|
2019-02-11 15:55:42 +07:00
|
|
|
err = tcf_chain_tp_insert(chain, &chain_info, tp_new);
|
2019-02-11 15:55:41 +07:00
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
|
|
|
|
|
|
|
if (tp) {
|
2019-11-02 21:17:47 +07:00
|
|
|
tcf_proto_destroy(tp_new, rtnl_held, false, NULL);
|
2019-02-11 15:55:41 +07:00
|
|
|
tp_new = tp;
|
2019-02-11 15:55:42 +07:00
|
|
|
} else if (err) {
|
2019-11-02 21:17:47 +07:00
|
|
|
tcf_proto_destroy(tp_new, rtnl_held, false, NULL);
|
2019-02-11 15:55:42 +07:00
|
|
|
tp_new = ERR_PTR(err);
|
2019-02-11 15:55:41 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return tp_new;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_chain_tp_delete_empty(struct tcf_chain *chain,
|
2019-02-11 15:55:45 +07:00
|
|
|
struct tcf_proto *tp, bool rtnl_held,
|
2019-02-11 15:55:41 +07:00
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
struct tcf_chain_info chain_info;
|
|
|
|
struct tcf_proto *tp_iter;
|
|
|
|
struct tcf_proto **pprev;
|
|
|
|
struct tcf_proto *next;
|
|
|
|
|
|
|
|
mutex_lock(&chain->filter_chain_lock);
|
|
|
|
|
|
|
|
/* Atomically find and remove tp from chain. */
|
|
|
|
for (pprev = &chain->filter_chain;
|
|
|
|
(tp_iter = tcf_chain_dereference(*pprev, chain));
|
|
|
|
pprev = &tp_iter->next) {
|
|
|
|
if (tp_iter == tp) {
|
|
|
|
chain_info.pprev = pprev;
|
|
|
|
chain_info.next = tp_iter->next;
|
|
|
|
WARN_ON(tp_iter->deleting);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Verify that tp still exists and no new filters were inserted
|
|
|
|
* concurrently.
|
|
|
|
* Mark tp for deletion if it is empty.
|
|
|
|
*/
|
net/sched: add delete_empty() to filters and use it in cls_flower
Revert "net/sched: cls_u32: fix refcount leak in the error path of
u32_change()", and fix the u32 refcount leak in a more generic way that
preserves the semantic of rule dumping.
On tc filters that don't support lockless insertion/removal, there is no
need to guard against concurrent insertion when a removal is in progress.
Therefore, for most of them we can avoid a full walk() when deleting, and
just decrease the refcount, like it was done on older Linux kernels.
This fixes situations where walk() was wrongly detecting a non-empty
filter, like it happened with cls_u32 in the error path of change(), thus
leading to failures in the following tdc selftests:
6aa7: (filter, u32) Add/Replace u32 with source match and invalid indev
6658: (filter, u32) Add/Replace u32 with custom hash table and invalid handle
74c2: (filter, u32) Add/Replace u32 filter with invalid hash table id
On cls_flower, and on (future) lockless filters, this check is necessary:
move all the check_empty() logic in a callback so that each filter
can have its own implementation. For cls_flower, it's sufficient to check
if no IDRs have been allocated.
This reverts commit 275c44aa194b7159d1191817b20e076f55f0e620.
Changes since v1:
- document the need for delete_empty() when TCF_PROTO_OPS_DOIT_UNLOCKED
is used, thanks to Vlad Buslov
- implement delete_empty() without doing fl_walk(), thanks to Vlad Buslov
- squash revert and new fix in a single patch, to be nice with bisect
tests that run tdc on u32 filter, thanks to Dave Miller
Fixes: 275c44aa194b ("net/sched: cls_u32: fix refcount leak in the error path of u32_change()")
Fixes: 6676d5e416ee ("net: sched: set dedicated tcf_walker flag when tp is empty")
Suggested-by: Jamal Hadi Salim <jhs@mojatatu.com>
Suggested-by: Vlad Buslov <vladbu@mellanox.com>
Signed-off-by: Davide Caratti <dcaratti@redhat.com>
Reviewed-by: Vlad Buslov <vladbu@mellanox.com>
Tested-by: Jamal Hadi Salim <jhs@mojatatu.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-12-28 22:36:58 +07:00
|
|
|
if (!tp_iter || !tcf_proto_check_delete(tp)) {
|
2019-02-11 15:55:41 +07:00
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 21:17:47 +07:00
|
|
|
tcf_proto_signal_destroying(chain, tp);
|
2019-02-11 15:55:41 +07:00
|
|
|
next = tcf_chain_dereference(chain_info.next, chain);
|
|
|
|
if (tp == chain->filter_chain)
|
|
|
|
tcf_chain0_head_change(chain, next);
|
|
|
|
RCU_INIT_POINTER(*chain_info.pprev, next);
|
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_put(tp, rtnl_held, extack);
|
2019-02-11 15:55:41 +07:00
|
|
|
}
|
|
|
|
|
2017-05-17 16:07:59 +07:00
|
|
|
static struct tcf_proto *tcf_chain_tp_find(struct tcf_chain *chain,
|
|
|
|
struct tcf_chain_info *chain_info,
|
|
|
|
u32 protocol, u32 prio,
|
|
|
|
bool prio_allocate)
|
|
|
|
{
|
|
|
|
struct tcf_proto **pprev;
|
|
|
|
struct tcf_proto *tp;
|
|
|
|
|
|
|
|
/* Check the chain for existence of proto-tcf with this priority */
|
|
|
|
for (pprev = &chain->filter_chain;
|
2019-02-11 15:55:38 +07:00
|
|
|
(tp = tcf_chain_dereference(*pprev, chain));
|
|
|
|
pprev = &tp->next) {
|
2017-05-17 16:07:59 +07:00
|
|
|
if (tp->prio >= prio) {
|
|
|
|
if (tp->prio == prio) {
|
|
|
|
if (prio_allocate ||
|
|
|
|
(tp->protocol != protocol && protocol))
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
} else {
|
|
|
|
tp = NULL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
chain_info->pprev = pprev;
|
2019-02-11 15:55:39 +07:00
|
|
|
if (tp) {
|
|
|
|
chain_info->next = tp->next;
|
|
|
|
tcf_proto_get(tp);
|
|
|
|
} else {
|
|
|
|
chain_info->next = NULL;
|
|
|
|
}
|
2017-05-17 16:07:59 +07:00
|
|
|
return tp;
|
|
|
|
}
|
|
|
|
|
2017-08-08 05:26:50 +07:00
|
|
|
static int tcf_fill_node(struct net *net, struct sk_buff *skb,
|
2018-01-17 17:46:51 +07:00
|
|
|
struct tcf_proto *tp, struct tcf_block *block,
|
|
|
|
struct Qdisc *q, u32 parent, void *fh,
|
2019-02-11 15:55:45 +07:00
|
|
|
u32 portid, u32 seq, u16 flags, int event,
|
|
|
|
bool rtnl_held)
|
2017-08-08 05:26:50 +07:00
|
|
|
{
|
|
|
|
struct tcmsg *tcm;
|
|
|
|
struct nlmsghdr *nlh;
|
|
|
|
unsigned char *b = skb_tail_pointer(skb);
|
|
|
|
|
|
|
|
nlh = nlmsg_put(skb, portid, seq, event, sizeof(*tcm), flags);
|
|
|
|
if (!nlh)
|
|
|
|
goto out_nlmsg_trim;
|
|
|
|
tcm = nlmsg_data(nlh);
|
|
|
|
tcm->tcm_family = AF_UNSPEC;
|
|
|
|
tcm->tcm__pad1 = 0;
|
|
|
|
tcm->tcm__pad2 = 0;
|
2018-01-17 17:46:51 +07:00
|
|
|
if (q) {
|
|
|
|
tcm->tcm_ifindex = qdisc_dev(q)->ifindex;
|
|
|
|
tcm->tcm_parent = parent;
|
|
|
|
} else {
|
|
|
|
tcm->tcm_ifindex = TCM_IFINDEX_MAGIC_BLOCK;
|
|
|
|
tcm->tcm_block_index = block->index;
|
|
|
|
}
|
2017-08-08 05:26:50 +07:00
|
|
|
tcm->tcm_info = TC_H_MAKE(tp->prio, tp->protocol);
|
|
|
|
if (nla_put_string(skb, TCA_KIND, tp->ops->kind))
|
|
|
|
goto nla_put_failure;
|
|
|
|
if (nla_put_u32(skb, TCA_CHAIN, tp->chain->index))
|
|
|
|
goto nla_put_failure;
|
|
|
|
if (!fh) {
|
|
|
|
tcm->tcm_handle = 0;
|
|
|
|
} else {
|
2019-02-11 15:55:45 +07:00
|
|
|
if (tp->ops->dump &&
|
|
|
|
tp->ops->dump(net, tp, fh, skb, tcm, rtnl_held) < 0)
|
2017-08-08 05:26:50 +07:00
|
|
|
goto nla_put_failure;
|
|
|
|
}
|
|
|
|
nlh->nlmsg_len = skb_tail_pointer(skb) - b;
|
|
|
|
return skb->len;
|
|
|
|
|
|
|
|
out_nlmsg_trim:
|
|
|
|
nla_put_failure:
|
|
|
|
nlmsg_trim(skb, b);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int tfilter_notify(struct net *net, struct sk_buff *oskb,
|
|
|
|
struct nlmsghdr *n, struct tcf_proto *tp,
|
2018-01-17 17:46:51 +07:00
|
|
|
struct tcf_block *block, struct Qdisc *q,
|
2019-02-11 15:55:45 +07:00
|
|
|
u32 parent, void *fh, int event, bool unicast,
|
|
|
|
bool rtnl_held)
|
2017-08-08 05:26:50 +07:00
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
u32 portid = oskb ? NETLINK_CB(oskb).portid : 0;
|
2019-03-11 17:15:54 +07:00
|
|
|
int err = 0;
|
2017-08-08 05:26:50 +07:00
|
|
|
|
|
|
|
skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
|
|
|
|
if (!skb)
|
|
|
|
return -ENOBUFS;
|
|
|
|
|
2018-01-17 17:46:51 +07:00
|
|
|
if (tcf_fill_node(net, skb, tp, block, q, parent, fh, portid,
|
2019-02-11 15:55:45 +07:00
|
|
|
n->nlmsg_seq, n->nlmsg_flags, event,
|
|
|
|
rtnl_held) <= 0) {
|
2017-08-08 05:26:50 +07:00
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unicast)
|
2019-03-11 17:15:54 +07:00
|
|
|
err = netlink_unicast(net->rtnl, skb, portid, MSG_DONTWAIT);
|
|
|
|
else
|
|
|
|
err = rtnetlink_send(skb, net, portid, RTNLGRP_TC,
|
|
|
|
n->nlmsg_flags & NLM_F_ECHO);
|
2017-08-08 05:26:50 +07:00
|
|
|
|
2019-03-11 17:15:54 +07:00
|
|
|
if (err > 0)
|
|
|
|
err = 0;
|
|
|
|
return err;
|
2017-08-08 05:26:50 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int tfilter_del_notify(struct net *net, struct sk_buff *oskb,
|
|
|
|
struct nlmsghdr *n, struct tcf_proto *tp,
|
2018-01-17 17:46:51 +07:00
|
|
|
struct tcf_block *block, struct Qdisc *q,
|
2018-01-18 23:20:50 +07:00
|
|
|
u32 parent, void *fh, bool unicast, bool *last,
|
2019-02-11 15:55:45 +07:00
|
|
|
bool rtnl_held, struct netlink_ext_ack *extack)
|
2017-08-08 05:26:50 +07:00
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
u32 portid = oskb ? NETLINK_CB(oskb).portid : 0;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
|
|
|
|
if (!skb)
|
|
|
|
return -ENOBUFS;
|
|
|
|
|
2018-01-17 17:46:51 +07:00
|
|
|
if (tcf_fill_node(net, skb, tp, block, q, parent, fh, portid,
|
2019-02-11 15:55:45 +07:00
|
|
|
n->nlmsg_seq, n->nlmsg_flags, RTM_DELTFILTER,
|
|
|
|
rtnl_held) <= 0) {
|
2018-01-18 23:20:50 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Failed to build del event notification");
|
2017-08-08 05:26:50 +07:00
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
err = tp->ops->delete(tp, fh, last, rtnl_held, extack);
|
2017-08-08 05:26:50 +07:00
|
|
|
if (err) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unicast)
|
2019-03-11 17:15:54 +07:00
|
|
|
err = netlink_unicast(net->rtnl, skb, portid, MSG_DONTWAIT);
|
|
|
|
else
|
|
|
|
err = rtnetlink_send(skb, net, portid, RTNLGRP_TC,
|
|
|
|
n->nlmsg_flags & NLM_F_ECHO);
|
2018-01-18 23:20:50 +07:00
|
|
|
if (err < 0)
|
|
|
|
NL_SET_ERR_MSG(extack, "Failed to send filter delete notification");
|
2019-03-11 17:15:54 +07:00
|
|
|
|
|
|
|
if (err > 0)
|
|
|
|
err = 0;
|
2018-01-18 23:20:50 +07:00
|
|
|
return err;
|
2017-08-08 05:26:50 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void tfilter_notify_chain(struct net *net, struct sk_buff *oskb,
|
2018-01-17 17:46:51 +07:00
|
|
|
struct tcf_block *block, struct Qdisc *q,
|
|
|
|
u32 parent, struct nlmsghdr *n,
|
2019-02-11 15:55:45 +07:00
|
|
|
struct tcf_chain *chain, int event,
|
|
|
|
bool rtnl_held)
|
2017-08-08 05:26:50 +07:00
|
|
|
{
|
|
|
|
struct tcf_proto *tp;
|
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
for (tp = tcf_get_next_proto(chain, NULL, rtnl_held);
|
|
|
|
tp; tp = tcf_get_next_proto(chain, tp, rtnl_held))
|
2018-01-17 17:46:51 +07:00
|
|
|
tfilter_notify(net, oskb, n, tp, block,
|
2019-02-11 15:55:45 +07:00
|
|
|
q, parent, NULL, event, false, rtnl_held);
|
2017-08-08 05:26:50 +07:00
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:44 +07:00
|
|
|
static void tfilter_put(struct tcf_proto *tp, void *fh)
|
|
|
|
{
|
|
|
|
if (tp->ops->put && fh)
|
|
|
|
tp->ops->put(tp, fh);
|
|
|
|
}
|
|
|
|
|
2018-05-31 13:52:53 +07:00
|
|
|
static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
|
2017-04-16 23:48:24 +07:00
|
|
|
struct netlink_ext_ack *extack)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2008-03-26 00:26:21 +07:00
|
|
|
struct net *net = sock_net(skb->sk);
|
2008-01-23 13:11:33 +07:00
|
|
|
struct nlattr *tca[TCA_MAX + 1];
|
2019-10-08 03:26:28 +07:00
|
|
|
char name[IFNAMSIZ];
|
2005-04-17 05:20:36 +07:00
|
|
|
struct tcmsg *t;
|
|
|
|
u32 protocol;
|
|
|
|
u32 prio;
|
2017-05-17 16:07:57 +07:00
|
|
|
bool prio_allocate;
|
2005-04-17 05:20:36 +07:00
|
|
|
u32 parent;
|
2017-05-17 16:08:01 +07:00
|
|
|
u32 chain_index;
|
2018-01-17 17:46:51 +07:00
|
|
|
struct Qdisc *q = NULL;
|
2017-05-17 16:07:59 +07:00
|
|
|
struct tcf_chain_info chain_info;
|
2017-05-17 16:08:01 +07:00
|
|
|
struct tcf_chain *chain = NULL;
|
2017-05-17 16:07:55 +07:00
|
|
|
struct tcf_block *block;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct tcf_proto *tp;
|
|
|
|
unsigned long cl;
|
2017-08-05 11:31:43 +07:00
|
|
|
void *fh;
|
2005-04-17 05:20:36 +07:00
|
|
|
int err;
|
net, sched: fix soft lockup in tc_classify
Shahar reported a soft lockup in tc_classify(), where we run into an
endless loop when walking the classifier chain due to tp->next == tp
which is a state we should never run into. The issue only seems to
trigger under load in the tc control path.
What happens is that in tc_ctl_tfilter(), thread A allocates a new
tp, initializes it, sets tp_created to 1, and calls into tp->ops->change()
with it. In that classifier callback we had to unlock/lock the rtnl
mutex and returned with -EAGAIN. One reason why we need to drop there
is, for example, that we need to request an action module to be loaded.
This happens via tcf_exts_validate() -> tcf_action_init/_1() meaning
after we loaded and found the requested action, we need to redo the
whole request so we don't race against others. While we had to unlock
rtnl in that time, thread B's request was processed next on that CPU.
Thread B added a new tp instance successfully to the classifier chain.
When thread A returned grabbing the rtnl mutex again, propagating -EAGAIN
and destroying its tp instance which never got linked, we goto replay
and redo A's request.
This time when walking the classifier chain in tc_ctl_tfilter() for
checking for existing tp instances we had a priority match and found
the tp instance that was created and linked by thread B. Now calling
again into tp->ops->change() with that tp was successful and returned
without error.
tp_created was never cleared in the second round, thus kernel thinks
that we need to link it into the classifier chain (once again). tp and
*back point to the same object due to the match we had earlier on. Thus
for thread B's already public tp, we reset tp->next to tp itself and
link it into the chain, which eventually causes the mentioned endless
loop in tc_classify() once a packet hits the data path.
Fix is to clear tp_created at the beginning of each request, also when
we replay it. On the paths that can cause -EAGAIN we already destroy
the original tp instance we had and on replay we really need to start
from scratch. It seems that this issue was first introduced in commit
12186be7d2e1 ("net_cls: fix unconfigured struct tcf_proto keeps chaining
and avoid kernel panic when we use cls_cgroup").
Fixes: 12186be7d2e1 ("net_cls: fix unconfigured struct tcf_proto keeps chaining and avoid kernel panic when we use cls_cgroup")
Reported-by: Shahar Klein <shahark@mellanox.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Cc: Cong Wang <xiyou.wangcong@gmail.com>
Acked-by: Eric Dumazet <edumazet@google.com>
Tested-by: Shahar Klein <shahark@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-12-22 00:04:11 +07:00
|
|
|
int tp_created;
|
2019-02-11 15:55:48 +07:00
|
|
|
bool rtnl_held = false;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2018-05-31 13:52:53 +07:00
|
|
|
if (!netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN))
|
2012-11-16 10:03:00 +07:00
|
|
|
return -EPERM;
|
2013-03-26 00:36:33 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
replay:
|
net, sched: fix soft lockup in tc_classify
Shahar reported a soft lockup in tc_classify(), where we run into an
endless loop when walking the classifier chain due to tp->next == tp
which is a state we should never run into. The issue only seems to
trigger under load in the tc control path.
What happens is that in tc_ctl_tfilter(), thread A allocates a new
tp, initializes it, sets tp_created to 1, and calls into tp->ops->change()
with it. In that classifier callback we had to unlock/lock the rtnl
mutex and returned with -EAGAIN. One reason why we need to drop there
is, for example, that we need to request an action module to be loaded.
This happens via tcf_exts_validate() -> tcf_action_init/_1() meaning
after we loaded and found the requested action, we need to redo the
whole request so we don't race against others. While we had to unlock
rtnl in that time, thread B's request was processed next on that CPU.
Thread B added a new tp instance successfully to the classifier chain.
When thread A returned grabbing the rtnl mutex again, propagating -EAGAIN
and destroying its tp instance which never got linked, we goto replay
and redo A's request.
This time when walking the classifier chain in tc_ctl_tfilter() for
checking for existing tp instances we had a priority match and found
the tp instance that was created and linked by thread B. Now calling
again into tp->ops->change() with that tp was successful and returned
without error.
tp_created was never cleared in the second round, thus kernel thinks
that we need to link it into the classifier chain (once again). tp and
*back point to the same object due to the match we had earlier on. Thus
for thread B's already public tp, we reset tp->next to tp itself and
link it into the chain, which eventually causes the mentioned endless
loop in tc_classify() once a packet hits the data path.
Fix is to clear tp_created at the beginning of each request, also when
we replay it. On the paths that can cause -EAGAIN we already destroy
the original tp instance we had and on replay we really need to start
from scratch. It seems that this issue was first introduced in commit
12186be7d2e1 ("net_cls: fix unconfigured struct tcf_proto keeps chaining
and avoid kernel panic when we use cls_cgroup").
Fixes: 12186be7d2e1 ("net_cls: fix unconfigured struct tcf_proto keeps chaining and avoid kernel panic when we use cls_cgroup")
Reported-by: Shahar Klein <shahark@mellanox.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Cc: Cong Wang <xiyou.wangcong@gmail.com>
Acked-by: Eric Dumazet <edumazet@google.com>
Tested-by: Shahar Klein <shahark@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-12-22 00:04:11 +07:00
|
|
|
tp_created = 0;
|
|
|
|
|
netlink: make validation more configurable for future strictness
We currently have two levels of strict validation:
1) liberal (default)
- undefined (type >= max) & NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
- garbage at end of message accepted
2) strict (opt-in)
- NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
Split out parsing strictness into four different options:
* TRAILING - check that there's no trailing data after parsing
attributes (in message or nested)
* MAXTYPE - reject attrs > max known type
* UNSPEC - reject attributes with NLA_UNSPEC policy entries
* STRICT_ATTRS - strictly validate attribute size
The default for future things should be *everything*.
The current *_strict() is a combination of TRAILING and MAXTYPE,
and is renamed to _deprecated_strict().
The current regular parsing has none of this, and is renamed to
*_parse_deprecated().
Additionally it allows us to selectively set one of the new flags
even on old policies. Notably, the UNSPEC flag could be useful in
this case, since it can be arranged (by filling in the policy) to
not be an incompatible userspace ABI change, but would then going
forward prevent forgetting attribute entries. Similar can apply
to the POLICY flag.
We end up with the following renames:
* nla_parse -> nla_parse_deprecated
* nla_parse_strict -> nla_parse_deprecated_strict
* nlmsg_parse -> nlmsg_parse_deprecated
* nlmsg_parse_strict -> nlmsg_parse_deprecated_strict
* nla_parse_nested -> nla_parse_nested_deprecated
* nla_validate_nested -> nla_validate_nested_deprecated
Using spatch, of course:
@@
expression TB, MAX, HEAD, LEN, POL, EXT;
@@
-nla_parse(TB, MAX, HEAD, LEN, POL, EXT)
+nla_parse_deprecated(TB, MAX, HEAD, LEN, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression TB, MAX, NLA, POL, EXT;
@@
-nla_parse_nested(TB, MAX, NLA, POL, EXT)
+nla_parse_nested_deprecated(TB, MAX, NLA, POL, EXT)
@@
expression START, MAX, POL, EXT;
@@
-nla_validate_nested(START, MAX, POL, EXT)
+nla_validate_nested_deprecated(START, MAX, POL, EXT)
@@
expression NLH, HDRLEN, MAX, POL, EXT;
@@
-nlmsg_validate(NLH, HDRLEN, MAX, POL, EXT)
+nlmsg_validate_deprecated(NLH, HDRLEN, MAX, POL, EXT)
For this patch, don't actually add the strict, non-renamed versions
yet so that it breaks compile if I get it wrong.
Also, while at it, make nla_validate and nla_parse go down to a
common __nla_validate_parse() function to avoid code duplication.
Ultimately, this allows us to have very strict validation for every
new caller of nla_parse()/nlmsg_parse() etc as re-introduced in the
next patch, while existing things will continue to work as is.
In effect then, this adds fully strict validation for any new command.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-26 19:07:28 +07:00
|
|
|
err = nlmsg_parse_deprecated(n, sizeof(*t), tca, TCA_MAX,
|
|
|
|
rtm_tca_policy, extack);
|
2013-03-26 00:36:33 +07:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2012-06-27 11:48:50 +07:00
|
|
|
t = nlmsg_data(n);
|
2005-04-17 05:20:36 +07:00
|
|
|
protocol = TC_H_MIN(t->tcm_info);
|
|
|
|
prio = TC_H_MAJ(t->tcm_info);
|
2017-05-17 16:07:57 +07:00
|
|
|
prio_allocate = false;
|
2005-04-17 05:20:36 +07:00
|
|
|
parent = t->tcm_parent;
|
2019-02-11 15:55:39 +07:00
|
|
|
tp = NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
cl = 0;
|
2019-02-11 15:55:48 +07:00
|
|
|
block = NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (prio == 0) {
|
2018-05-31 13:52:53 +07:00
|
|
|
/* If no priority is provided by the user,
|
|
|
|
* we allocate one.
|
|
|
|
*/
|
|
|
|
if (n->nlmsg_flags & NLM_F_CREATE) {
|
|
|
|
prio = TC_H_MAKE(0x80000000U, 0U);
|
|
|
|
prio_allocate = true;
|
|
|
|
} else {
|
2018-01-18 23:20:50 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Invalid filter command with priority of zero");
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENOENT;
|
net, cls: allow for deleting all filters for given parent
Add a possibility where the user can just specify the parent and
all filters under that parent are then being purged. Currently,
for example for scripting, one needs to specify pref/prio to have
a well-defined number for 'tc filter del' command for addressing
the previously created instance or additionally filter handle in
case of priorities being the same. Improve usage by allowing the
option for tc to specify the parent and removing the whole chain
for that given parent.
Example usage after patch, no tc changes required:
# tc qdisc replace dev foo clsact
# tc filter add dev foo egress bpf da obj ./bpf.o
# tc filter add dev foo egress bpf da obj ./bpf.o
# tc filter show dev foo egress
filter protocol all pref 49151 bpf
filter protocol all pref 49151 bpf handle 0x1 bpf.o:[classifier] direct-action
filter protocol all pref 49152 bpf
filter protocol all pref 49152 bpf handle 0x1 bpf.o:[classifier] direct-action
# tc filter del dev foo egress
# tc filter show dev foo egress
#
Previously, RTM_DELTFILTER requests with invalid prio of 0 were
rejected, so only netlink requests with RTM_NEWTFILTER and NLM_F_CREATE
flag were allowed where the kernel would auto-generate a pref/prio.
We can piggyback on that and use prio of 0 as a wildcard for
requests of RTM_DELTFILTER.
For notifying tc netlink monitoring users (e.g. libnl uses this
for caching), there are two options, that is, sending individual
tfilter_notify() notifications for each tcf_proto, or sending a
single one indicating wildcard removal. I tried both and there
are pros and cons for each, eventually I decided for sending
individual tfilter_notify(), so that user space can support this
seamlessly and there won't be a mess of changing each and every
application to make sure expectations from the kernel won't break
when they don't understand single notification. Since linear chains
don't really scale, I expect only a handful of classifiers to be
attached at max for a given parent anyway.
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Acked-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-06-11 04:10:22 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Find head of filter chain. */
|
|
|
|
|
2019-02-11 15:55:48 +07:00
|
|
|
err = __tcf_qdisc_find(net, &q, &parent, t->tcm_ifindex, false, extack);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2019-10-08 03:26:28 +07:00
|
|
|
if (tcf_proto_check_kind(tca[TCA_KIND], name)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified TC filter name too long");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:48 +07:00
|
|
|
/* Take rtnl mutex if rtnl_held was set to true on previous iteration,
|
|
|
|
* block is shared (no qdisc found), qdisc is not unlocked, classifier
|
|
|
|
* type is not specified, classifier is not unlocked.
|
|
|
|
*/
|
|
|
|
if (rtnl_held ||
|
|
|
|
(q && !(q->ops->cl_ops->flags & QDISC_CLASS_OPS_DOIT_UNLOCKED)) ||
|
2019-10-08 03:26:28 +07:00
|
|
|
!tcf_proto_is_unlocked(name)) {
|
2019-02-11 15:55:48 +07:00
|
|
|
rtnl_held = true;
|
|
|
|
rtnl_lock();
|
|
|
|
}
|
|
|
|
|
|
|
|
err = __tcf_qdisc_cl_find(q, parent, &cl, t->tcm_ifindex, extack);
|
|
|
|
if (err)
|
|
|
|
goto errout;
|
|
|
|
|
|
|
|
block = __tcf_block_find(net, q, cl, t->tcm_ifindex, t->tcm_block_index,
|
|
|
|
extack);
|
2018-05-31 13:52:53 +07:00
|
|
|
if (IS_ERR(block)) {
|
|
|
|
err = PTR_ERR(block);
|
|
|
|
goto errout;
|
2017-02-09 20:38:58 +07:00
|
|
|
}
|
2017-05-17 16:08:01 +07:00
|
|
|
|
|
|
|
chain_index = tca[TCA_CHAIN] ? nla_get_u32(tca[TCA_CHAIN]) : 0;
|
|
|
|
if (chain_index > TC_ACT_EXT_VAL_MASK) {
|
2018-01-18 23:20:50 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Specified chain index exceeds upper limit");
|
2017-05-17 16:08:01 +07:00
|
|
|
err = -EINVAL;
|
|
|
|
goto errout;
|
|
|
|
}
|
2018-05-31 13:52:53 +07:00
|
|
|
chain = tcf_chain_get(block, chain_index, true);
|
2017-05-17 16:08:01 +07:00
|
|
|
if (!chain) {
|
2018-08-28 01:58:43 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Cannot create specified filter chain");
|
2018-05-31 13:52:53 +07:00
|
|
|
err = -ENOMEM;
|
net, cls: allow for deleting all filters for given parent
Add a possibility where the user can just specify the parent and
all filters under that parent are then being purged. Currently,
for example for scripting, one needs to specify pref/prio to have
a well-defined number for 'tc filter del' command for addressing
the previously created instance or additionally filter handle in
case of priorities being the same. Improve usage by allowing the
option for tc to specify the parent and removing the whole chain
for that given parent.
Example usage after patch, no tc changes required:
# tc qdisc replace dev foo clsact
# tc filter add dev foo egress bpf da obj ./bpf.o
# tc filter add dev foo egress bpf da obj ./bpf.o
# tc filter show dev foo egress
filter protocol all pref 49151 bpf
filter protocol all pref 49151 bpf handle 0x1 bpf.o:[classifier] direct-action
filter protocol all pref 49152 bpf
filter protocol all pref 49152 bpf handle 0x1 bpf.o:[classifier] direct-action
# tc filter del dev foo egress
# tc filter show dev foo egress
#
Previously, RTM_DELTFILTER requests with invalid prio of 0 were
rejected, so only netlink requests with RTM_NEWTFILTER and NLM_F_CREATE
flag were allowed where the kernel would auto-generate a pref/prio.
We can piggyback on that and use prio of 0 as a wildcard for
requests of RTM_DELTFILTER.
For notifying tc netlink monitoring users (e.g. libnl uses this
for caching), there are two options, that is, sending individual
tfilter_notify() notifications for each tcf_proto, or sending a
single one indicating wildcard removal. I tried both and there
are pros and cons for each, eventually I decided for sending
individual tfilter_notify(), so that user space can support this
seamlessly and there won't be a mess of changing each and every
application to make sure expectations from the kernel won't break
when they don't understand single notification. Since linear chains
don't really scale, I expect only a handful of classifiers to be
attached at max for a given parent anyway.
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Acked-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-06-11 04:10:22 +07:00
|
|
|
goto errout;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2019-02-11 15:55:38 +07:00
|
|
|
mutex_lock(&chain->filter_chain_lock);
|
2017-05-17 16:07:59 +07:00
|
|
|
tp = tcf_chain_tp_find(chain, &chain_info, protocol,
|
|
|
|
prio, prio_allocate);
|
|
|
|
if (IS_ERR(tp)) {
|
2018-01-18 23:20:50 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Filter with specified priority/protocol not found");
|
2017-05-17 16:07:59 +07:00
|
|
|
err = PTR_ERR(tp);
|
2019-02-11 15:55:38 +07:00
|
|
|
goto errout_locked;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (tp == NULL) {
|
2019-02-11 15:55:41 +07:00
|
|
|
struct tcf_proto *tp_new = NULL;
|
|
|
|
|
2019-02-11 15:55:42 +07:00
|
|
|
if (chain->flushing) {
|
|
|
|
err = -EAGAIN;
|
|
|
|
goto errout_locked;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/* Proto-tcf does not exist, create new one */
|
|
|
|
|
2017-02-09 20:38:58 +07:00
|
|
|
if (tca[TCA_KIND] == NULL || !protocol) {
|
2018-01-18 23:20:50 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Filter kind and protocol must be specified");
|
2017-02-09 20:38:58 +07:00
|
|
|
err = -EINVAL;
|
2019-02-11 15:55:38 +07:00
|
|
|
goto errout_locked;
|
2017-02-09 20:38:58 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2018-05-31 13:52:53 +07:00
|
|
|
if (!(n->nlmsg_flags & NLM_F_CREATE)) {
|
2018-01-18 23:20:50 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Need both RTM_NEWTFILTER and NLM_F_CREATE to create a new filter");
|
2017-02-09 20:38:58 +07:00
|
|
|
err = -ENOENT;
|
2019-02-11 15:55:38 +07:00
|
|
|
goto errout_locked;
|
2017-02-09 20:38:58 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2017-05-17 16:07:57 +07:00
|
|
|
if (prio_allocate)
|
2019-02-11 15:55:38 +07:00
|
|
|
prio = tcf_auto_prio(tcf_chain_tp_prev(chain,
|
|
|
|
&chain_info));
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2019-02-11 15:55:38 +07:00
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
2020-01-22 02:02:20 +07:00
|
|
|
tp_new = tcf_proto_create(name, protocol, prio, chain,
|
|
|
|
rtnl_held, extack);
|
2019-02-11 15:55:41 +07:00
|
|
|
if (IS_ERR(tp_new)) {
|
|
|
|
err = PTR_ERR(tp_new);
|
2019-02-11 15:55:42 +07:00
|
|
|
goto errout_tp;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2019-02-11 15:55:38 +07:00
|
|
|
|
2009-06-02 16:17:34 +07:00
|
|
|
tp_created = 1;
|
2019-02-11 15:55:45 +07:00
|
|
|
tp = tcf_chain_tp_insert_unique(chain, tp_new, protocol, prio,
|
|
|
|
rtnl_held);
|
2019-02-11 15:55:42 +07:00
|
|
|
if (IS_ERR(tp)) {
|
|
|
|
err = PTR_ERR(tp);
|
|
|
|
goto errout_tp;
|
|
|
|
}
|
2019-02-11 15:55:38 +07:00
|
|
|
} else {
|
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
2017-02-09 20:38:58 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2019-02-11 15:55:41 +07:00
|
|
|
if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], tp->ops->kind)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified filter kind does not match existing one");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
fh = tp->ops->get(tp, t->tcm_handle);
|
|
|
|
|
2017-08-05 11:31:43 +07:00
|
|
|
if (!fh) {
|
2018-05-31 13:52:53 +07:00
|
|
|
if (!(n->nlmsg_flags & NLM_F_CREATE)) {
|
2018-01-18 23:20:50 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Need both RTM_NEWTFILTER and NLM_F_CREATE to create a new filter");
|
2017-02-09 20:38:58 +07:00
|
|
|
err = -ENOENT;
|
2005-04-17 05:20:36 +07:00
|
|
|
goto errout;
|
2017-02-09 20:38:58 +07:00
|
|
|
}
|
2018-05-31 13:52:53 +07:00
|
|
|
} else if (n->nlmsg_flags & NLM_F_EXCL) {
|
2019-02-11 15:55:44 +07:00
|
|
|
tfilter_put(tp, fh);
|
2018-05-31 13:52:53 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Filter already exists");
|
|
|
|
err = -EEXIST;
|
|
|
|
goto errout;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2018-07-23 14:23:07 +07:00
|
|
|
if (chain->tmplt_ops && chain->tmplt_ops != tp->ops) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Chain template is set to a different filter kind");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
2014-04-26 03:54:06 +07:00
|
|
|
err = tp->ops->change(net, skb, tp, cl, t->tcm_handle, tca, &fh,
|
2018-01-18 23:20:51 +07:00
|
|
|
n->nlmsg_flags & NLM_F_CREATE ? TCA_ACT_NOREPLACE : TCA_ACT_REPLACE,
|
2019-02-11 15:55:45 +07:00
|
|
|
rtnl_held, extack);
|
2019-02-11 15:55:44 +07:00
|
|
|
if (err == 0) {
|
2018-01-17 17:46:51 +07:00
|
|
|
tfilter_notify(net, skb, n, tp, block, q, parent, fh,
|
2019-02-11 15:55:45 +07:00
|
|
|
RTM_NEWTFILTER, false, rtnl_held);
|
2019-02-11 15:55:44 +07:00
|
|
|
tfilter_put(tp, fh);
|
2019-07-21 21:44:12 +07:00
|
|
|
/* q pointer is NULL for shared blocks */
|
|
|
|
if (q)
|
|
|
|
q->flags &= ~TCQ_F_CAN_BYPASS;
|
2019-02-11 15:55:44 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
errout:
|
2019-02-11 15:55:41 +07:00
|
|
|
if (err && tp_created)
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_chain_tp_delete_empty(chain, tp, rtnl_held, NULL);
|
2019-02-11 15:55:42 +07:00
|
|
|
errout_tp:
|
2019-02-11 15:55:39 +07:00
|
|
|
if (chain) {
|
|
|
|
if (tp && !IS_ERR(tp))
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_put(tp, rtnl_held, NULL);
|
2019-02-11 15:55:39 +07:00
|
|
|
if (!tp_created)
|
|
|
|
tcf_chain_put(chain);
|
|
|
|
}
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_block_release(q, block, rtnl_held);
|
2019-02-11 15:55:48 +07:00
|
|
|
|
|
|
|
if (rtnl_held)
|
|
|
|
rtnl_unlock();
|
|
|
|
|
|
|
|
if (err == -EAGAIN) {
|
|
|
|
/* Take rtnl lock in case EAGAIN is caused by concurrent flush
|
|
|
|
* of target chain.
|
|
|
|
*/
|
|
|
|
rtnl_held = true;
|
2005-04-17 05:20:36 +07:00
|
|
|
/* Replay the request. */
|
|
|
|
goto replay;
|
2019-02-11 15:55:48 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
return err;
|
2019-02-11 15:55:38 +07:00
|
|
|
|
|
|
|
errout_locked:
|
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
|
|
|
goto errout;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2018-05-31 13:52:53 +07:00
|
|
|
static int tc_del_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
struct net *net = sock_net(skb->sk);
|
|
|
|
struct nlattr *tca[TCA_MAX + 1];
|
2019-10-08 03:26:28 +07:00
|
|
|
char name[IFNAMSIZ];
|
2018-05-31 13:52:53 +07:00
|
|
|
struct tcmsg *t;
|
|
|
|
u32 protocol;
|
|
|
|
u32 prio;
|
|
|
|
u32 parent;
|
|
|
|
u32 chain_index;
|
|
|
|
struct Qdisc *q = NULL;
|
|
|
|
struct tcf_chain_info chain_info;
|
|
|
|
struct tcf_chain *chain = NULL;
|
2019-02-11 15:55:48 +07:00
|
|
|
struct tcf_block *block = NULL;
|
2018-05-31 13:52:53 +07:00
|
|
|
struct tcf_proto *tp = NULL;
|
|
|
|
unsigned long cl = 0;
|
|
|
|
void *fh = NULL;
|
|
|
|
int err;
|
2019-02-11 15:55:48 +07:00
|
|
|
bool rtnl_held = false;
|
2018-05-31 13:52:53 +07:00
|
|
|
|
|
|
|
if (!netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN))
|
|
|
|
return -EPERM;
|
|
|
|
|
netlink: make validation more configurable for future strictness
We currently have two levels of strict validation:
1) liberal (default)
- undefined (type >= max) & NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
- garbage at end of message accepted
2) strict (opt-in)
- NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
Split out parsing strictness into four different options:
* TRAILING - check that there's no trailing data after parsing
attributes (in message or nested)
* MAXTYPE - reject attrs > max known type
* UNSPEC - reject attributes with NLA_UNSPEC policy entries
* STRICT_ATTRS - strictly validate attribute size
The default for future things should be *everything*.
The current *_strict() is a combination of TRAILING and MAXTYPE,
and is renamed to _deprecated_strict().
The current regular parsing has none of this, and is renamed to
*_parse_deprecated().
Additionally it allows us to selectively set one of the new flags
even on old policies. Notably, the UNSPEC flag could be useful in
this case, since it can be arranged (by filling in the policy) to
not be an incompatible userspace ABI change, but would then going
forward prevent forgetting attribute entries. Similar can apply
to the POLICY flag.
We end up with the following renames:
* nla_parse -> nla_parse_deprecated
* nla_parse_strict -> nla_parse_deprecated_strict
* nlmsg_parse -> nlmsg_parse_deprecated
* nlmsg_parse_strict -> nlmsg_parse_deprecated_strict
* nla_parse_nested -> nla_parse_nested_deprecated
* nla_validate_nested -> nla_validate_nested_deprecated
Using spatch, of course:
@@
expression TB, MAX, HEAD, LEN, POL, EXT;
@@
-nla_parse(TB, MAX, HEAD, LEN, POL, EXT)
+nla_parse_deprecated(TB, MAX, HEAD, LEN, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression TB, MAX, NLA, POL, EXT;
@@
-nla_parse_nested(TB, MAX, NLA, POL, EXT)
+nla_parse_nested_deprecated(TB, MAX, NLA, POL, EXT)
@@
expression START, MAX, POL, EXT;
@@
-nla_validate_nested(START, MAX, POL, EXT)
+nla_validate_nested_deprecated(START, MAX, POL, EXT)
@@
expression NLH, HDRLEN, MAX, POL, EXT;
@@
-nlmsg_validate(NLH, HDRLEN, MAX, POL, EXT)
+nlmsg_validate_deprecated(NLH, HDRLEN, MAX, POL, EXT)
For this patch, don't actually add the strict, non-renamed versions
yet so that it breaks compile if I get it wrong.
Also, while at it, make nla_validate and nla_parse go down to a
common __nla_validate_parse() function to avoid code duplication.
Ultimately, this allows us to have very strict validation for every
new caller of nla_parse()/nlmsg_parse() etc as re-introduced in the
next patch, while existing things will continue to work as is.
In effect then, this adds fully strict validation for any new command.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-26 19:07:28 +07:00
|
|
|
err = nlmsg_parse_deprecated(n, sizeof(*t), tca, TCA_MAX,
|
|
|
|
rtm_tca_policy, extack);
|
2018-05-31 13:52:53 +07:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
t = nlmsg_data(n);
|
|
|
|
protocol = TC_H_MIN(t->tcm_info);
|
|
|
|
prio = TC_H_MAJ(t->tcm_info);
|
|
|
|
parent = t->tcm_parent;
|
|
|
|
|
|
|
|
if (prio == 0 && (protocol || t->tcm_handle || tca[TCA_KIND])) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Cannot flush filters with protocol, handle or kind set");
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Find head of filter chain. */
|
|
|
|
|
2019-02-11 15:55:48 +07:00
|
|
|
err = __tcf_qdisc_find(net, &q, &parent, t->tcm_ifindex, false, extack);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2019-10-08 03:26:28 +07:00
|
|
|
if (tcf_proto_check_kind(tca[TCA_KIND], name)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified TC filter name too long");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout;
|
|
|
|
}
|
2019-02-11 15:55:48 +07:00
|
|
|
/* Take rtnl mutex if flushing whole chain, block is shared (no qdisc
|
|
|
|
* found), qdisc is not unlocked, classifier type is not specified,
|
|
|
|
* classifier is not unlocked.
|
|
|
|
*/
|
|
|
|
if (!prio ||
|
|
|
|
(q && !(q->ops->cl_ops->flags & QDISC_CLASS_OPS_DOIT_UNLOCKED)) ||
|
2019-10-08 03:26:28 +07:00
|
|
|
!tcf_proto_is_unlocked(name)) {
|
2019-02-11 15:55:48 +07:00
|
|
|
rtnl_held = true;
|
|
|
|
rtnl_lock();
|
|
|
|
}
|
|
|
|
|
|
|
|
err = __tcf_qdisc_cl_find(q, parent, &cl, t->tcm_ifindex, extack);
|
|
|
|
if (err)
|
|
|
|
goto errout;
|
|
|
|
|
|
|
|
block = __tcf_block_find(net, q, cl, t->tcm_ifindex, t->tcm_block_index,
|
|
|
|
extack);
|
2018-05-31 13:52:53 +07:00
|
|
|
if (IS_ERR(block)) {
|
|
|
|
err = PTR_ERR(block);
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
|
|
|
chain_index = tca[TCA_CHAIN] ? nla_get_u32(tca[TCA_CHAIN]) : 0;
|
|
|
|
if (chain_index > TC_ACT_EXT_VAL_MASK) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified chain index exceeds upper limit");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
chain = tcf_chain_get(block, chain_index, false);
|
|
|
|
if (!chain) {
|
2018-08-03 16:08:47 +07:00
|
|
|
/* User requested flush on non-existent chain. Nothing to do,
|
|
|
|
* so just return success.
|
|
|
|
*/
|
|
|
|
if (prio == 0) {
|
|
|
|
err = 0;
|
|
|
|
goto errout;
|
|
|
|
}
|
2018-05-31 13:52:53 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Cannot find specified filter chain");
|
2018-08-28 01:58:44 +07:00
|
|
|
err = -ENOENT;
|
2018-05-31 13:52:53 +07:00
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prio == 0) {
|
|
|
|
tfilter_notify_chain(net, skb, block, q, parent, n,
|
2019-02-11 15:55:45 +07:00
|
|
|
chain, RTM_DELTFILTER, rtnl_held);
|
|
|
|
tcf_chain_flush(chain, rtnl_held);
|
2018-05-31 13:52:53 +07:00
|
|
|
err = 0;
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:38 +07:00
|
|
|
mutex_lock(&chain->filter_chain_lock);
|
2018-05-31 13:52:53 +07:00
|
|
|
tp = tcf_chain_tp_find(chain, &chain_info, protocol,
|
|
|
|
prio, false);
|
|
|
|
if (!tp || IS_ERR(tp)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Filter with specified priority/protocol not found");
|
2018-06-04 22:32:23 +07:00
|
|
|
err = tp ? PTR_ERR(tp) : -ENOENT;
|
2019-02-11 15:55:38 +07:00
|
|
|
goto errout_locked;
|
2018-05-31 13:52:53 +07:00
|
|
|
} else if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], tp->ops->kind)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified filter kind does not match existing one");
|
|
|
|
err = -EINVAL;
|
2019-02-11 15:55:38 +07:00
|
|
|
goto errout_locked;
|
|
|
|
} else if (t->tcm_handle == 0) {
|
2019-11-02 21:17:47 +07:00
|
|
|
tcf_proto_signal_destroying(chain, tp);
|
2019-02-11 15:55:38 +07:00
|
|
|
tcf_chain_tp_remove(chain, &chain_info, tp);
|
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
|
|
|
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_put(tp, rtnl_held, NULL);
|
2019-02-11 15:55:38 +07:00
|
|
|
tfilter_notify(net, skb, n, tp, block, q, parent, fh,
|
2019-02-11 15:55:45 +07:00
|
|
|
RTM_DELTFILTER, false, rtnl_held);
|
2019-02-11 15:55:38 +07:00
|
|
|
err = 0;
|
2018-05-31 13:52:53 +07:00
|
|
|
goto errout;
|
|
|
|
}
|
2019-02-11 15:55:38 +07:00
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
2018-05-31 13:52:53 +07:00
|
|
|
|
|
|
|
fh = tp->ops->get(tp, t->tcm_handle);
|
|
|
|
|
|
|
|
if (!fh) {
|
2019-02-11 15:55:38 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Specified filter handle not found");
|
|
|
|
err = -ENOENT;
|
2018-05-31 13:52:53 +07:00
|
|
|
} else {
|
|
|
|
bool last;
|
|
|
|
|
|
|
|
err = tfilter_del_notify(net, skb, n, tp, block,
|
|
|
|
q, parent, fh, false, &last,
|
2019-02-11 15:55:45 +07:00
|
|
|
rtnl_held, extack);
|
|
|
|
|
2018-05-31 13:52:53 +07:00
|
|
|
if (err)
|
|
|
|
goto errout;
|
2019-02-11 15:55:41 +07:00
|
|
|
if (last)
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_chain_tp_delete_empty(chain, tp, rtnl_held, extack);
|
2018-05-31 13:52:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
errout:
|
2019-02-11 15:55:39 +07:00
|
|
|
if (chain) {
|
|
|
|
if (tp && !IS_ERR(tp))
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_put(tp, rtnl_held, NULL);
|
2018-05-31 13:52:53 +07:00
|
|
|
tcf_chain_put(chain);
|
2019-02-11 15:55:39 +07:00
|
|
|
}
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_block_release(q, block, rtnl_held);
|
2019-02-11 15:55:48 +07:00
|
|
|
|
|
|
|
if (rtnl_held)
|
|
|
|
rtnl_unlock();
|
|
|
|
|
2018-05-31 13:52:53 +07:00
|
|
|
return err;
|
2019-02-11 15:55:38 +07:00
|
|
|
|
|
|
|
errout_locked:
|
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
|
|
|
goto errout;
|
2018-05-31 13:52:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int tc_get_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
struct net *net = sock_net(skb->sk);
|
|
|
|
struct nlattr *tca[TCA_MAX + 1];
|
2019-10-08 03:26:28 +07:00
|
|
|
char name[IFNAMSIZ];
|
2018-05-31 13:52:53 +07:00
|
|
|
struct tcmsg *t;
|
|
|
|
u32 protocol;
|
|
|
|
u32 prio;
|
|
|
|
u32 parent;
|
|
|
|
u32 chain_index;
|
|
|
|
struct Qdisc *q = NULL;
|
|
|
|
struct tcf_chain_info chain_info;
|
|
|
|
struct tcf_chain *chain = NULL;
|
2019-02-11 15:55:48 +07:00
|
|
|
struct tcf_block *block = NULL;
|
2018-05-31 13:52:53 +07:00
|
|
|
struct tcf_proto *tp = NULL;
|
|
|
|
unsigned long cl = 0;
|
|
|
|
void *fh = NULL;
|
|
|
|
int err;
|
2019-02-11 15:55:48 +07:00
|
|
|
bool rtnl_held = false;
|
2018-05-31 13:52:53 +07:00
|
|
|
|
netlink: make validation more configurable for future strictness
We currently have two levels of strict validation:
1) liberal (default)
- undefined (type >= max) & NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
- garbage at end of message accepted
2) strict (opt-in)
- NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
Split out parsing strictness into four different options:
* TRAILING - check that there's no trailing data after parsing
attributes (in message or nested)
* MAXTYPE - reject attrs > max known type
* UNSPEC - reject attributes with NLA_UNSPEC policy entries
* STRICT_ATTRS - strictly validate attribute size
The default for future things should be *everything*.
The current *_strict() is a combination of TRAILING and MAXTYPE,
and is renamed to _deprecated_strict().
The current regular parsing has none of this, and is renamed to
*_parse_deprecated().
Additionally it allows us to selectively set one of the new flags
even on old policies. Notably, the UNSPEC flag could be useful in
this case, since it can be arranged (by filling in the policy) to
not be an incompatible userspace ABI change, but would then going
forward prevent forgetting attribute entries. Similar can apply
to the POLICY flag.
We end up with the following renames:
* nla_parse -> nla_parse_deprecated
* nla_parse_strict -> nla_parse_deprecated_strict
* nlmsg_parse -> nlmsg_parse_deprecated
* nlmsg_parse_strict -> nlmsg_parse_deprecated_strict
* nla_parse_nested -> nla_parse_nested_deprecated
* nla_validate_nested -> nla_validate_nested_deprecated
Using spatch, of course:
@@
expression TB, MAX, HEAD, LEN, POL, EXT;
@@
-nla_parse(TB, MAX, HEAD, LEN, POL, EXT)
+nla_parse_deprecated(TB, MAX, HEAD, LEN, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression TB, MAX, NLA, POL, EXT;
@@
-nla_parse_nested(TB, MAX, NLA, POL, EXT)
+nla_parse_nested_deprecated(TB, MAX, NLA, POL, EXT)
@@
expression START, MAX, POL, EXT;
@@
-nla_validate_nested(START, MAX, POL, EXT)
+nla_validate_nested_deprecated(START, MAX, POL, EXT)
@@
expression NLH, HDRLEN, MAX, POL, EXT;
@@
-nlmsg_validate(NLH, HDRLEN, MAX, POL, EXT)
+nlmsg_validate_deprecated(NLH, HDRLEN, MAX, POL, EXT)
For this patch, don't actually add the strict, non-renamed versions
yet so that it breaks compile if I get it wrong.
Also, while at it, make nla_validate and nla_parse go down to a
common __nla_validate_parse() function to avoid code duplication.
Ultimately, this allows us to have very strict validation for every
new caller of nla_parse()/nlmsg_parse() etc as re-introduced in the
next patch, while existing things will continue to work as is.
In effect then, this adds fully strict validation for any new command.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-26 19:07:28 +07:00
|
|
|
err = nlmsg_parse_deprecated(n, sizeof(*t), tca, TCA_MAX,
|
|
|
|
rtm_tca_policy, extack);
|
2018-05-31 13:52:53 +07:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
t = nlmsg_data(n);
|
|
|
|
protocol = TC_H_MIN(t->tcm_info);
|
|
|
|
prio = TC_H_MAJ(t->tcm_info);
|
|
|
|
parent = t->tcm_parent;
|
|
|
|
|
|
|
|
if (prio == 0) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Invalid filter command with priority of zero");
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Find head of filter chain. */
|
|
|
|
|
2019-02-11 15:55:48 +07:00
|
|
|
err = __tcf_qdisc_find(net, &q, &parent, t->tcm_ifindex, false, extack);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2019-10-08 03:26:28 +07:00
|
|
|
if (tcf_proto_check_kind(tca[TCA_KIND], name)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified TC filter name too long");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout;
|
|
|
|
}
|
2019-02-11 15:55:48 +07:00
|
|
|
/* Take rtnl mutex if block is shared (no qdisc found), qdisc is not
|
|
|
|
* unlocked, classifier type is not specified, classifier is not
|
|
|
|
* unlocked.
|
|
|
|
*/
|
|
|
|
if ((q && !(q->ops->cl_ops->flags & QDISC_CLASS_OPS_DOIT_UNLOCKED)) ||
|
2019-10-08 03:26:28 +07:00
|
|
|
!tcf_proto_is_unlocked(name)) {
|
2019-02-11 15:55:48 +07:00
|
|
|
rtnl_held = true;
|
|
|
|
rtnl_lock();
|
|
|
|
}
|
|
|
|
|
|
|
|
err = __tcf_qdisc_cl_find(q, parent, &cl, t->tcm_ifindex, extack);
|
|
|
|
if (err)
|
|
|
|
goto errout;
|
|
|
|
|
|
|
|
block = __tcf_block_find(net, q, cl, t->tcm_ifindex, t->tcm_block_index,
|
|
|
|
extack);
|
2018-05-31 13:52:53 +07:00
|
|
|
if (IS_ERR(block)) {
|
|
|
|
err = PTR_ERR(block);
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
|
|
|
chain_index = tca[TCA_CHAIN] ? nla_get_u32(tca[TCA_CHAIN]) : 0;
|
|
|
|
if (chain_index > TC_ACT_EXT_VAL_MASK) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified chain index exceeds upper limit");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
chain = tcf_chain_get(block, chain_index, false);
|
|
|
|
if (!chain) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Cannot find specified filter chain");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:38 +07:00
|
|
|
mutex_lock(&chain->filter_chain_lock);
|
2018-05-31 13:52:53 +07:00
|
|
|
tp = tcf_chain_tp_find(chain, &chain_info, protocol,
|
|
|
|
prio, false);
|
2019-02-11 15:55:38 +07:00
|
|
|
mutex_unlock(&chain->filter_chain_lock);
|
2018-05-31 13:52:53 +07:00
|
|
|
if (!tp || IS_ERR(tp)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Filter with specified priority/protocol not found");
|
2018-06-04 22:32:23 +07:00
|
|
|
err = tp ? PTR_ERR(tp) : -ENOENT;
|
2018-05-31 13:52:53 +07:00
|
|
|
goto errout;
|
|
|
|
} else if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], tp->ops->kind)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified filter kind does not match existing one");
|
|
|
|
err = -EINVAL;
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
|
|
|
fh = tp->ops->get(tp, t->tcm_handle);
|
|
|
|
|
|
|
|
if (!fh) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified filter handle not found");
|
|
|
|
err = -ENOENT;
|
|
|
|
} else {
|
|
|
|
err = tfilter_notify(net, skb, n, tp, block, q, parent,
|
2019-02-11 15:55:45 +07:00
|
|
|
fh, RTM_NEWTFILTER, true, rtnl_held);
|
2018-05-31 13:52:53 +07:00
|
|
|
if (err < 0)
|
|
|
|
NL_SET_ERR_MSG(extack, "Failed to send filter notify message");
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:44 +07:00
|
|
|
tfilter_put(tp, fh);
|
2018-05-31 13:52:53 +07:00
|
|
|
errout:
|
2019-02-11 15:55:39 +07:00
|
|
|
if (chain) {
|
|
|
|
if (tp && !IS_ERR(tp))
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_put(tp, rtnl_held, NULL);
|
2018-05-31 13:52:53 +07:00
|
|
|
tcf_chain_put(chain);
|
2019-02-11 15:55:39 +07:00
|
|
|
}
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_block_release(q, block, rtnl_held);
|
2019-02-11 15:55:48 +07:00
|
|
|
|
|
|
|
if (rtnl_held)
|
|
|
|
rtnl_unlock();
|
|
|
|
|
2018-05-31 13:52:53 +07:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2008-01-21 17:26:41 +07:00
|
|
|
struct tcf_dump_args {
|
2005-04-17 05:20:36 +07:00
|
|
|
struct tcf_walker w;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct netlink_callback *cb;
|
2018-01-17 17:46:51 +07:00
|
|
|
struct tcf_block *block;
|
2017-10-13 19:01:05 +07:00
|
|
|
struct Qdisc *q;
|
|
|
|
u32 parent;
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
|
|
|
|
2017-08-05 11:31:43 +07:00
|
|
|
static int tcf_node_dump(struct tcf_proto *tp, void *n, struct tcf_walker *arg)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2008-01-21 17:26:41 +07:00
|
|
|
struct tcf_dump_args *a = (void *)arg;
|
2014-01-10 07:14:01 +07:00
|
|
|
struct net *net = sock_net(a->skb->sk);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2018-01-17 17:46:51 +07:00
|
|
|
return tcf_fill_node(net, a->skb, tp, a->block, a->q, a->parent,
|
2017-10-13 19:01:05 +07:00
|
|
|
n, NETLINK_CB(a->cb->skb).portid,
|
2016-09-18 19:45:33 +07:00
|
|
|
a->cb->nlh->nlmsg_seq, NLM_F_MULTI,
|
2019-02-11 15:55:45 +07:00
|
|
|
RTM_NEWTFILTER, true);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2017-10-13 19:01:05 +07:00
|
|
|
static bool tcf_chain_dump(struct tcf_chain *chain, struct Qdisc *q, u32 parent,
|
|
|
|
struct sk_buff *skb, struct netlink_callback *cb,
|
2017-05-17 16:08:00 +07:00
|
|
|
long index_start, long *p_index)
|
|
|
|
{
|
|
|
|
struct net *net = sock_net(skb->sk);
|
2018-01-17 17:46:51 +07:00
|
|
|
struct tcf_block *block = chain->block;
|
2017-05-17 16:08:00 +07:00
|
|
|
struct tcmsg *tcm = nlmsg_data(cb->nlh);
|
2019-02-11 15:55:40 +07:00
|
|
|
struct tcf_proto *tp, *tp_prev;
|
2017-05-17 16:08:00 +07:00
|
|
|
struct tcf_dump_args arg;
|
|
|
|
|
2019-02-11 15:55:40 +07:00
|
|
|
for (tp = __tcf_get_next_proto(chain, NULL);
|
|
|
|
tp;
|
|
|
|
tp_prev = tp,
|
|
|
|
tp = __tcf_get_next_proto(chain, tp),
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_put(tp_prev, true, NULL),
|
2019-02-11 15:55:40 +07:00
|
|
|
(*p_index)++) {
|
2017-05-17 16:08:00 +07:00
|
|
|
if (*p_index < index_start)
|
|
|
|
continue;
|
|
|
|
if (TC_H_MAJ(tcm->tcm_info) &&
|
|
|
|
TC_H_MAJ(tcm->tcm_info) != tp->prio)
|
|
|
|
continue;
|
|
|
|
if (TC_H_MIN(tcm->tcm_info) &&
|
|
|
|
TC_H_MIN(tcm->tcm_info) != tp->protocol)
|
|
|
|
continue;
|
|
|
|
if (*p_index > index_start)
|
|
|
|
memset(&cb->args[1], 0,
|
|
|
|
sizeof(cb->args) - sizeof(cb->args[0]));
|
|
|
|
if (cb->args[1] == 0) {
|
2018-07-17 19:58:14 +07:00
|
|
|
if (tcf_fill_node(net, skb, tp, block, q, parent, NULL,
|
2017-05-17 16:08:00 +07:00
|
|
|
NETLINK_CB(cb->skb).portid,
|
|
|
|
cb->nlh->nlmsg_seq, NLM_F_MULTI,
|
2019-02-11 15:55:45 +07:00
|
|
|
RTM_NEWTFILTER, true) <= 0)
|
2019-02-11 15:55:40 +07:00
|
|
|
goto errout;
|
2017-05-17 16:08:00 +07:00
|
|
|
cb->args[1] = 1;
|
|
|
|
}
|
|
|
|
if (!tp->ops->walk)
|
|
|
|
continue;
|
|
|
|
arg.w.fn = tcf_node_dump;
|
|
|
|
arg.skb = skb;
|
|
|
|
arg.cb = cb;
|
2018-01-17 17:46:51 +07:00
|
|
|
arg.block = block;
|
2017-10-13 19:01:05 +07:00
|
|
|
arg.q = q;
|
|
|
|
arg.parent = parent;
|
2017-05-17 16:08:00 +07:00
|
|
|
arg.w.stop = 0;
|
|
|
|
arg.w.skip = cb->args[1] - 1;
|
|
|
|
arg.w.count = 0;
|
2018-07-09 17:29:11 +07:00
|
|
|
arg.w.cookie = cb->args[2];
|
2019-02-11 15:55:45 +07:00
|
|
|
tp->ops->walk(tp, &arg.w, true);
|
2018-07-09 17:29:11 +07:00
|
|
|
cb->args[2] = arg.w.cookie;
|
2017-05-17 16:08:00 +07:00
|
|
|
cb->args[1] = arg.w.count + 1;
|
|
|
|
if (arg.w.stop)
|
2019-02-11 15:55:40 +07:00
|
|
|
goto errout;
|
2017-05-17 16:08:00 +07:00
|
|
|
}
|
2017-05-17 16:08:01 +07:00
|
|
|
return true;
|
2019-02-11 15:55:40 +07:00
|
|
|
|
|
|
|
errout:
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_proto_put(tp, true, NULL);
|
2019-02-11 15:55:40 +07:00
|
|
|
return false;
|
2017-05-17 16:08:00 +07:00
|
|
|
}
|
|
|
|
|
2009-11-06 11:57:26 +07:00
|
|
|
/* called with RTNL */
|
2005-04-17 05:20:36 +07:00
|
|
|
static int tc_dump_tfilter(struct sk_buff *skb, struct netlink_callback *cb)
|
|
|
|
{
|
2019-02-11 15:55:36 +07:00
|
|
|
struct tcf_chain *chain, *chain_prev;
|
2008-03-26 00:26:21 +07:00
|
|
|
struct net *net = sock_net(skb->sk);
|
2017-05-17 16:08:01 +07:00
|
|
|
struct nlattr *tca[TCA_MAX + 1];
|
2018-01-17 17:46:51 +07:00
|
|
|
struct Qdisc *q = NULL;
|
2017-05-17 16:07:55 +07:00
|
|
|
struct tcf_block *block;
|
2012-06-27 11:48:50 +07:00
|
|
|
struct tcmsg *tcm = nlmsg_data(cb->nlh);
|
2017-05-17 16:08:00 +07:00
|
|
|
long index_start;
|
|
|
|
long index;
|
2017-10-13 19:01:05 +07:00
|
|
|
u32 parent;
|
2017-05-17 16:08:01 +07:00
|
|
|
int err;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-03-27 13:47:04 +07:00
|
|
|
if (nlmsg_len(cb->nlh) < sizeof(*tcm))
|
2005-04-17 05:20:36 +07:00
|
|
|
return skb->len;
|
2017-05-17 16:08:01 +07:00
|
|
|
|
netlink: make validation more configurable for future strictness
We currently have two levels of strict validation:
1) liberal (default)
- undefined (type >= max) & NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
- garbage at end of message accepted
2) strict (opt-in)
- NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
Split out parsing strictness into four different options:
* TRAILING - check that there's no trailing data after parsing
attributes (in message or nested)
* MAXTYPE - reject attrs > max known type
* UNSPEC - reject attributes with NLA_UNSPEC policy entries
* STRICT_ATTRS - strictly validate attribute size
The default for future things should be *everything*.
The current *_strict() is a combination of TRAILING and MAXTYPE,
and is renamed to _deprecated_strict().
The current regular parsing has none of this, and is renamed to
*_parse_deprecated().
Additionally it allows us to selectively set one of the new flags
even on old policies. Notably, the UNSPEC flag could be useful in
this case, since it can be arranged (by filling in the policy) to
not be an incompatible userspace ABI change, but would then going
forward prevent forgetting attribute entries. Similar can apply
to the POLICY flag.
We end up with the following renames:
* nla_parse -> nla_parse_deprecated
* nla_parse_strict -> nla_parse_deprecated_strict
* nlmsg_parse -> nlmsg_parse_deprecated
* nlmsg_parse_strict -> nlmsg_parse_deprecated_strict
* nla_parse_nested -> nla_parse_nested_deprecated
* nla_validate_nested -> nla_validate_nested_deprecated
Using spatch, of course:
@@
expression TB, MAX, HEAD, LEN, POL, EXT;
@@
-nla_parse(TB, MAX, HEAD, LEN, POL, EXT)
+nla_parse_deprecated(TB, MAX, HEAD, LEN, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression TB, MAX, NLA, POL, EXT;
@@
-nla_parse_nested(TB, MAX, NLA, POL, EXT)
+nla_parse_nested_deprecated(TB, MAX, NLA, POL, EXT)
@@
expression START, MAX, POL, EXT;
@@
-nla_validate_nested(START, MAX, POL, EXT)
+nla_validate_nested_deprecated(START, MAX, POL, EXT)
@@
expression NLH, HDRLEN, MAX, POL, EXT;
@@
-nlmsg_validate(NLH, HDRLEN, MAX, POL, EXT)
+nlmsg_validate_deprecated(NLH, HDRLEN, MAX, POL, EXT)
For this patch, don't actually add the strict, non-renamed versions
yet so that it breaks compile if I get it wrong.
Also, while at it, make nla_validate and nla_parse go down to a
common __nla_validate_parse() function to avoid code duplication.
Ultimately, this allows us to have very strict validation for every
new caller of nla_parse()/nlmsg_parse() etc as re-introduced in the
next patch, while existing things will continue to work as is.
In effect then, this adds fully strict validation for any new command.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-26 19:07:28 +07:00
|
|
|
err = nlmsg_parse_deprecated(cb->nlh, sizeof(*tcm), tca, TCA_MAX,
|
|
|
|
NULL, cb->extack);
|
2017-05-17 16:08:01 +07:00
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2018-01-17 17:46:51 +07:00
|
|
|
if (tcm->tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK) {
|
2018-09-24 23:22:58 +07:00
|
|
|
block = tcf_block_refcnt_get(net, tcm->tcm_block_index);
|
2018-01-17 17:46:51 +07:00
|
|
|
if (!block)
|
|
|
|
goto out;
|
2018-01-18 22:14:49 +07:00
|
|
|
/* If we work with block index, q is NULL and parent value
|
|
|
|
* will never be used in the following code. The check
|
|
|
|
* in tcf_fill_node prevents it. However, compiler does not
|
|
|
|
* see that far, so set parent to zero to silence the warning
|
|
|
|
* about parent being uninitialized.
|
|
|
|
*/
|
|
|
|
parent = 0;
|
2017-10-13 19:01:05 +07:00
|
|
|
} else {
|
2018-01-17 17:46:51 +07:00
|
|
|
const struct Qdisc_class_ops *cops;
|
|
|
|
struct net_device *dev;
|
|
|
|
unsigned long cl = 0;
|
|
|
|
|
|
|
|
dev = __dev_get_by_index(net, tcm->tcm_ifindex);
|
|
|
|
if (!dev)
|
|
|
|
return skb->len;
|
|
|
|
|
|
|
|
parent = tcm->tcm_parent;
|
|
|
|
if (!parent) {
|
|
|
|
q = dev->qdisc;
|
|
|
|
parent = q->handle;
|
|
|
|
} else {
|
|
|
|
q = qdisc_lookup(dev, TC_H_MAJ(tcm->tcm_parent));
|
|
|
|
}
|
|
|
|
if (!q)
|
|
|
|
goto out;
|
|
|
|
cops = q->ops->cl_ops;
|
|
|
|
if (!cops)
|
net_sched: remove tc class reference counting
For TC classes, their ->get() and ->put() are always paired, and the
reference counting is completely useless, because:
1) For class modification and dumping paths, we already hold RTNL lock,
so all of these ->get(),->change(),->put() are atomic.
2) For filter bindiing/unbinding, we use other reference counter than
this one, and they should have RTNL lock too.
3) For ->qlen_notify(), it is special because it is called on ->enqueue()
path, but we already hold qdisc tree lock there, and we hold this
tree lock when graft or delete the class too, so it should not be gone
or changed until we release the tree lock.
Therefore, this patch removes ->get() and ->put(), but:
1) Adds a new ->find() to find the pointer to a class by classid, no
refcnt.
2) Move the original class destroy upon the last refcnt into ->delete(),
right after releasing tree lock. This is fine because the class is
already removed from hash when holding the lock.
For those who also use ->put() as ->unbind(), just rename them to reflect
this change.
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-08-25 06:51:29 +07:00
|
|
|
goto out;
|
2018-01-17 17:46:51 +07:00
|
|
|
if (!cops->tcf_block)
|
|
|
|
goto out;
|
|
|
|
if (TC_H_MIN(tcm->tcm_parent)) {
|
|
|
|
cl = cops->find(q, tcm->tcm_parent);
|
|
|
|
if (cl == 0)
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
block = cops->tcf_block(q, cl, NULL);
|
|
|
|
if (!block)
|
|
|
|
goto out;
|
|
|
|
if (tcf_block_shared(block))
|
|
|
|
q = NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2017-05-17 16:08:00 +07:00
|
|
|
index_start = cb->args[0];
|
|
|
|
index = 0;
|
2017-05-17 16:08:01 +07:00
|
|
|
|
2019-02-11 15:55:36 +07:00
|
|
|
for (chain = __tcf_get_next_chain(block, NULL);
|
|
|
|
chain;
|
|
|
|
chain_prev = chain,
|
|
|
|
chain = __tcf_get_next_chain(block, chain),
|
|
|
|
tcf_chain_put(chain_prev)) {
|
2017-05-17 16:08:01 +07:00
|
|
|
if (tca[TCA_CHAIN] &&
|
|
|
|
nla_get_u32(tca[TCA_CHAIN]) != chain->index)
|
|
|
|
continue;
|
2017-10-13 19:01:05 +07:00
|
|
|
if (!tcf_chain_dump(chain, q, parent, skb, cb,
|
2018-02-20 03:32:51 +07:00
|
|
|
index_start, &index)) {
|
2019-02-11 15:55:36 +07:00
|
|
|
tcf_chain_put(chain);
|
2018-02-20 03:32:51 +07:00
|
|
|
err = -EMSGSIZE;
|
2017-05-17 16:08:01 +07:00
|
|
|
break;
|
2018-02-20 03:32:51 +07:00
|
|
|
}
|
2017-05-17 16:08:01 +07:00
|
|
|
}
|
|
|
|
|
2018-09-24 23:22:58 +07:00
|
|
|
if (tcm->tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK)
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_block_refcnt_put(block, true);
|
2017-05-17 16:08:00 +07:00
|
|
|
cb->args[0] = index;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
out:
|
2018-02-20 03:32:51 +07:00
|
|
|
/* If we did no progress, the error (EMSGSIZE) is real */
|
|
|
|
if (skb->len == 0 && err)
|
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
return skb->len;
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:37 +07:00
|
|
|
static int tc_chain_fill_node(const struct tcf_proto_ops *tmplt_ops,
|
|
|
|
void *tmplt_priv, u32 chain_index,
|
|
|
|
struct net *net, struct sk_buff *skb,
|
|
|
|
struct tcf_block *block,
|
2018-07-23 14:23:06 +07:00
|
|
|
u32 portid, u32 seq, u16 flags, int event)
|
|
|
|
{
|
|
|
|
unsigned char *b = skb_tail_pointer(skb);
|
2018-07-23 14:23:07 +07:00
|
|
|
const struct tcf_proto_ops *ops;
|
2018-07-23 14:23:06 +07:00
|
|
|
struct nlmsghdr *nlh;
|
|
|
|
struct tcmsg *tcm;
|
2018-07-23 14:23:07 +07:00
|
|
|
void *priv;
|
|
|
|
|
2019-02-11 15:55:37 +07:00
|
|
|
ops = tmplt_ops;
|
|
|
|
priv = tmplt_priv;
|
2018-07-23 14:23:06 +07:00
|
|
|
|
|
|
|
nlh = nlmsg_put(skb, portid, seq, event, sizeof(*tcm), flags);
|
|
|
|
if (!nlh)
|
|
|
|
goto out_nlmsg_trim;
|
|
|
|
tcm = nlmsg_data(nlh);
|
|
|
|
tcm->tcm_family = AF_UNSPEC;
|
|
|
|
tcm->tcm__pad1 = 0;
|
|
|
|
tcm->tcm__pad2 = 0;
|
|
|
|
tcm->tcm_handle = 0;
|
|
|
|
if (block->q) {
|
|
|
|
tcm->tcm_ifindex = qdisc_dev(block->q)->ifindex;
|
|
|
|
tcm->tcm_parent = block->q->handle;
|
|
|
|
} else {
|
|
|
|
tcm->tcm_ifindex = TCM_IFINDEX_MAGIC_BLOCK;
|
|
|
|
tcm->tcm_block_index = block->index;
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:37 +07:00
|
|
|
if (nla_put_u32(skb, TCA_CHAIN, chain_index))
|
2018-07-23 14:23:06 +07:00
|
|
|
goto nla_put_failure;
|
|
|
|
|
2018-07-23 14:23:07 +07:00
|
|
|
if (ops) {
|
|
|
|
if (nla_put_string(skb, TCA_KIND, ops->kind))
|
|
|
|
goto nla_put_failure;
|
|
|
|
if (ops->tmplt_dump(skb, net, priv) < 0)
|
|
|
|
goto nla_put_failure;
|
|
|
|
}
|
|
|
|
|
2018-07-23 14:23:06 +07:00
|
|
|
nlh->nlmsg_len = skb_tail_pointer(skb) - b;
|
|
|
|
return skb->len;
|
|
|
|
|
|
|
|
out_nlmsg_trim:
|
|
|
|
nla_put_failure:
|
|
|
|
nlmsg_trim(skb, b);
|
|
|
|
return -EMSGSIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int tc_chain_notify(struct tcf_chain *chain, struct sk_buff *oskb,
|
|
|
|
u32 seq, u16 flags, int event, bool unicast)
|
|
|
|
{
|
|
|
|
u32 portid = oskb ? NETLINK_CB(oskb).portid : 0;
|
|
|
|
struct tcf_block *block = chain->block;
|
|
|
|
struct net *net = block->net;
|
|
|
|
struct sk_buff *skb;
|
2019-03-11 17:15:54 +07:00
|
|
|
int err = 0;
|
2018-07-23 14:23:06 +07:00
|
|
|
|
|
|
|
skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
|
|
|
|
if (!skb)
|
|
|
|
return -ENOBUFS;
|
|
|
|
|
2019-02-11 15:55:37 +07:00
|
|
|
if (tc_chain_fill_node(chain->tmplt_ops, chain->tmplt_priv,
|
|
|
|
chain->index, net, skb, block, portid,
|
2018-07-23 14:23:06 +07:00
|
|
|
seq, flags, event) <= 0) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unicast)
|
2019-03-11 17:15:54 +07:00
|
|
|
err = netlink_unicast(net->rtnl, skb, portid, MSG_DONTWAIT);
|
|
|
|
else
|
|
|
|
err = rtnetlink_send(skb, net, portid, RTNLGRP_TC,
|
|
|
|
flags & NLM_F_ECHO);
|
2018-07-23 14:23:06 +07:00
|
|
|
|
2019-03-11 17:15:54 +07:00
|
|
|
if (err > 0)
|
|
|
|
err = 0;
|
|
|
|
return err;
|
2018-07-23 14:23:06 +07:00
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:37 +07:00
|
|
|
static int tc_chain_notify_delete(const struct tcf_proto_ops *tmplt_ops,
|
|
|
|
void *tmplt_priv, u32 chain_index,
|
|
|
|
struct tcf_block *block, struct sk_buff *oskb,
|
|
|
|
u32 seq, u16 flags, bool unicast)
|
|
|
|
{
|
|
|
|
u32 portid = oskb ? NETLINK_CB(oskb).portid : 0;
|
|
|
|
struct net *net = block->net;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
|
|
|
|
skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
|
|
|
|
if (!skb)
|
|
|
|
return -ENOBUFS;
|
|
|
|
|
|
|
|
if (tc_chain_fill_node(tmplt_ops, tmplt_priv, chain_index, net, skb,
|
|
|
|
block, portid, seq, flags, RTM_DELCHAIN) <= 0) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unicast)
|
|
|
|
return netlink_unicast(net->rtnl, skb, portid, MSG_DONTWAIT);
|
|
|
|
|
|
|
|
return rtnetlink_send(skb, net, portid, RTNLGRP_TC, flags & NLM_F_ECHO);
|
|
|
|
}
|
|
|
|
|
2018-07-23 14:23:07 +07:00
|
|
|
static int tc_chain_tmplt_add(struct tcf_chain *chain, struct net *net,
|
|
|
|
struct nlattr **tca,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
const struct tcf_proto_ops *ops;
|
2019-12-08 02:34:45 +07:00
|
|
|
char name[IFNAMSIZ];
|
2018-07-23 14:23:07 +07:00
|
|
|
void *tmplt_priv;
|
|
|
|
|
|
|
|
/* If kind is not set, user did not specify template. */
|
|
|
|
if (!tca[TCA_KIND])
|
|
|
|
return 0;
|
|
|
|
|
2019-12-08 02:34:45 +07:00
|
|
|
if (tcf_proto_check_kind(tca[TCA_KIND], name)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified TC chain template name too long");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ops = tcf_proto_lookup_ops(name, true, extack);
|
2018-07-23 14:23:07 +07:00
|
|
|
if (IS_ERR(ops))
|
|
|
|
return PTR_ERR(ops);
|
|
|
|
if (!ops->tmplt_create || !ops->tmplt_destroy || !ops->tmplt_dump) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Chain templates are not supported with specified classifier");
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmplt_priv = ops->tmplt_create(net, chain, tca, extack);
|
|
|
|
if (IS_ERR(tmplt_priv)) {
|
|
|
|
module_put(ops->owner);
|
|
|
|
return PTR_ERR(tmplt_priv);
|
|
|
|
}
|
|
|
|
chain->tmplt_ops = ops;
|
|
|
|
chain->tmplt_priv = tmplt_priv;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:37 +07:00
|
|
|
static void tc_chain_tmplt_del(const struct tcf_proto_ops *tmplt_ops,
|
|
|
|
void *tmplt_priv)
|
2018-07-23 14:23:07 +07:00
|
|
|
{
|
|
|
|
/* If template ops are set, no work to do for us. */
|
2019-02-11 15:55:37 +07:00
|
|
|
if (!tmplt_ops)
|
2018-07-23 14:23:07 +07:00
|
|
|
return;
|
|
|
|
|
2019-02-11 15:55:37 +07:00
|
|
|
tmplt_ops->tmplt_destroy(tmplt_priv);
|
|
|
|
module_put(tmplt_ops->owner);
|
2018-07-23 14:23:07 +07:00
|
|
|
}
|
|
|
|
|
2018-07-23 14:23:06 +07:00
|
|
|
/* Add/delete/get a chain */
|
|
|
|
|
|
|
|
static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
struct net *net = sock_net(skb->sk);
|
|
|
|
struct nlattr *tca[TCA_MAX + 1];
|
|
|
|
struct tcmsg *t;
|
|
|
|
u32 parent;
|
|
|
|
u32 chain_index;
|
|
|
|
struct Qdisc *q = NULL;
|
|
|
|
struct tcf_chain *chain = NULL;
|
|
|
|
struct tcf_block *block;
|
|
|
|
unsigned long cl;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (n->nlmsg_type != RTM_GETCHAIN &&
|
|
|
|
!netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN))
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
replay:
|
netlink: make validation more configurable for future strictness
We currently have two levels of strict validation:
1) liberal (default)
- undefined (type >= max) & NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
- garbage at end of message accepted
2) strict (opt-in)
- NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
Split out parsing strictness into four different options:
* TRAILING - check that there's no trailing data after parsing
attributes (in message or nested)
* MAXTYPE - reject attrs > max known type
* UNSPEC - reject attributes with NLA_UNSPEC policy entries
* STRICT_ATTRS - strictly validate attribute size
The default for future things should be *everything*.
The current *_strict() is a combination of TRAILING and MAXTYPE,
and is renamed to _deprecated_strict().
The current regular parsing has none of this, and is renamed to
*_parse_deprecated().
Additionally it allows us to selectively set one of the new flags
even on old policies. Notably, the UNSPEC flag could be useful in
this case, since it can be arranged (by filling in the policy) to
not be an incompatible userspace ABI change, but would then going
forward prevent forgetting attribute entries. Similar can apply
to the POLICY flag.
We end up with the following renames:
* nla_parse -> nla_parse_deprecated
* nla_parse_strict -> nla_parse_deprecated_strict
* nlmsg_parse -> nlmsg_parse_deprecated
* nlmsg_parse_strict -> nlmsg_parse_deprecated_strict
* nla_parse_nested -> nla_parse_nested_deprecated
* nla_validate_nested -> nla_validate_nested_deprecated
Using spatch, of course:
@@
expression TB, MAX, HEAD, LEN, POL, EXT;
@@
-nla_parse(TB, MAX, HEAD, LEN, POL, EXT)
+nla_parse_deprecated(TB, MAX, HEAD, LEN, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression TB, MAX, NLA, POL, EXT;
@@
-nla_parse_nested(TB, MAX, NLA, POL, EXT)
+nla_parse_nested_deprecated(TB, MAX, NLA, POL, EXT)
@@
expression START, MAX, POL, EXT;
@@
-nla_validate_nested(START, MAX, POL, EXT)
+nla_validate_nested_deprecated(START, MAX, POL, EXT)
@@
expression NLH, HDRLEN, MAX, POL, EXT;
@@
-nlmsg_validate(NLH, HDRLEN, MAX, POL, EXT)
+nlmsg_validate_deprecated(NLH, HDRLEN, MAX, POL, EXT)
For this patch, don't actually add the strict, non-renamed versions
yet so that it breaks compile if I get it wrong.
Also, while at it, make nla_validate and nla_parse go down to a
common __nla_validate_parse() function to avoid code duplication.
Ultimately, this allows us to have very strict validation for every
new caller of nla_parse()/nlmsg_parse() etc as re-introduced in the
next patch, while existing things will continue to work as is.
In effect then, this adds fully strict validation for any new command.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-26 19:07:28 +07:00
|
|
|
err = nlmsg_parse_deprecated(n, sizeof(*t), tca, TCA_MAX,
|
|
|
|
rtm_tca_policy, extack);
|
2018-07-23 14:23:06 +07:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
t = nlmsg_data(n);
|
|
|
|
parent = t->tcm_parent;
|
|
|
|
cl = 0;
|
|
|
|
|
|
|
|
block = tcf_block_find(net, &q, &parent, &cl,
|
|
|
|
t->tcm_ifindex, t->tcm_block_index, extack);
|
|
|
|
if (IS_ERR(block))
|
|
|
|
return PTR_ERR(block);
|
|
|
|
|
|
|
|
chain_index = tca[TCA_CHAIN] ? nla_get_u32(tca[TCA_CHAIN]) : 0;
|
|
|
|
if (chain_index > TC_ACT_EXT_VAL_MASK) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Specified chain index exceeds upper limit");
|
2018-09-24 23:22:53 +07:00
|
|
|
err = -EINVAL;
|
|
|
|
goto errout_block;
|
2018-07-23 14:23:06 +07:00
|
|
|
}
|
2019-02-11 15:55:34 +07:00
|
|
|
|
|
|
|
mutex_lock(&block->lock);
|
2018-07-23 14:23:06 +07:00
|
|
|
chain = tcf_chain_lookup(block, chain_index);
|
|
|
|
if (n->nlmsg_type == RTM_NEWCHAIN) {
|
|
|
|
if (chain) {
|
2018-08-01 17:36:55 +07:00
|
|
|
if (tcf_chain_held_by_acts_only(chain)) {
|
2018-07-27 14:45:05 +07:00
|
|
|
/* The chain exists only because there is
|
2018-08-01 17:36:55 +07:00
|
|
|
* some action referencing it.
|
2018-07-27 14:45:05 +07:00
|
|
|
*/
|
|
|
|
tcf_chain_hold(chain);
|
|
|
|
} else {
|
|
|
|
NL_SET_ERR_MSG(extack, "Filter chain already exists");
|
2018-09-24 23:22:53 +07:00
|
|
|
err = -EEXIST;
|
2019-02-11 15:55:34 +07:00
|
|
|
goto errout_block_locked;
|
2018-07-27 14:45:05 +07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!(n->nlmsg_flags & NLM_F_CREATE)) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Need both RTM_NEWCHAIN and NLM_F_CREATE to create a new chain");
|
2018-09-24 23:22:53 +07:00
|
|
|
err = -ENOENT;
|
2019-02-11 15:55:34 +07:00
|
|
|
goto errout_block_locked;
|
2018-07-27 14:45:05 +07:00
|
|
|
}
|
|
|
|
chain = tcf_chain_create(block, chain_index);
|
|
|
|
if (!chain) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Failed to create filter chain");
|
2018-09-24 23:22:53 +07:00
|
|
|
err = -ENOMEM;
|
2019-02-11 15:55:34 +07:00
|
|
|
goto errout_block_locked;
|
2018-07-27 14:45:05 +07:00
|
|
|
}
|
2018-07-23 14:23:06 +07:00
|
|
|
}
|
|
|
|
} else {
|
2018-08-01 17:36:55 +07:00
|
|
|
if (!chain || tcf_chain_held_by_acts_only(chain)) {
|
2018-07-23 14:23:06 +07:00
|
|
|
NL_SET_ERR_MSG(extack, "Cannot find specified filter chain");
|
2018-09-24 23:22:53 +07:00
|
|
|
err = -EINVAL;
|
2019-02-11 15:55:34 +07:00
|
|
|
goto errout_block_locked;
|
2018-07-23 14:23:06 +07:00
|
|
|
}
|
|
|
|
tcf_chain_hold(chain);
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:55:34 +07:00
|
|
|
if (n->nlmsg_type == RTM_NEWCHAIN) {
|
|
|
|
/* Modifying chain requires holding parent block lock. In case
|
|
|
|
* the chain was successfully added, take a reference to the
|
|
|
|
* chain. This ensures that an empty chain does not disappear at
|
|
|
|
* the end of this function.
|
|
|
|
*/
|
|
|
|
tcf_chain_hold(chain);
|
|
|
|
chain->explicitly_created = true;
|
|
|
|
}
|
|
|
|
mutex_unlock(&block->lock);
|
|
|
|
|
2018-07-23 14:23:06 +07:00
|
|
|
switch (n->nlmsg_type) {
|
|
|
|
case RTM_NEWCHAIN:
|
2018-07-23 14:23:07 +07:00
|
|
|
err = tc_chain_tmplt_add(chain, net, tca, extack);
|
2019-02-11 15:55:34 +07:00
|
|
|
if (err) {
|
|
|
|
tcf_chain_put_explicitly_created(chain);
|
2018-07-23 14:23:07 +07:00
|
|
|
goto errout;
|
2019-02-11 15:55:34 +07:00
|
|
|
}
|
|
|
|
|
2018-07-23 14:23:06 +07:00
|
|
|
tc_chain_notify(chain, NULL, 0, NLM_F_CREATE | NLM_F_EXCL,
|
|
|
|
RTM_NEWCHAIN, false);
|
|
|
|
break;
|
|
|
|
case RTM_DELCHAIN:
|
2018-09-12 04:22:23 +07:00
|
|
|
tfilter_notify_chain(net, skb, block, q, parent, n,
|
2019-02-11 15:55:45 +07:00
|
|
|
chain, RTM_DELTFILTER, true);
|
2018-07-23 14:23:06 +07:00
|
|
|
/* Flush the chain first as the user requested chain removal. */
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_chain_flush(chain, true);
|
2018-07-23 14:23:06 +07:00
|
|
|
/* In case the chain was successfully deleted, put a reference
|
|
|
|
* to the chain previously taken during addition.
|
|
|
|
*/
|
|
|
|
tcf_chain_put_explicitly_created(chain);
|
|
|
|
break;
|
|
|
|
case RTM_GETCHAIN:
|
|
|
|
err = tc_chain_notify(chain, skb, n->nlmsg_seq,
|
|
|
|
n->nlmsg_seq, n->nlmsg_type, true);
|
|
|
|
if (err < 0)
|
|
|
|
NL_SET_ERR_MSG(extack, "Failed to send chain notify message");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
err = -EOPNOTSUPP;
|
|
|
|
NL_SET_ERR_MSG(extack, "Unsupported message type");
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
|
|
|
errout:
|
|
|
|
tcf_chain_put(chain);
|
2018-09-24 23:22:53 +07:00
|
|
|
errout_block:
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_block_release(q, block, true);
|
2018-07-23 14:23:06 +07:00
|
|
|
if (err == -EAGAIN)
|
|
|
|
/* Replay the request. */
|
|
|
|
goto replay;
|
|
|
|
return err;
|
2019-02-11 15:55:34 +07:00
|
|
|
|
|
|
|
errout_block_locked:
|
|
|
|
mutex_unlock(&block->lock);
|
|
|
|
goto errout_block;
|
2018-07-23 14:23:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* called with RTNL */
|
|
|
|
static int tc_dump_chain(struct sk_buff *skb, struct netlink_callback *cb)
|
|
|
|
{
|
|
|
|
struct net *net = sock_net(skb->sk);
|
|
|
|
struct nlattr *tca[TCA_MAX + 1];
|
|
|
|
struct Qdisc *q = NULL;
|
|
|
|
struct tcf_block *block;
|
|
|
|
struct tcmsg *tcm = nlmsg_data(cb->nlh);
|
2019-02-25 22:45:44 +07:00
|
|
|
struct tcf_chain *chain;
|
2018-07-23 14:23:06 +07:00
|
|
|
long index_start;
|
|
|
|
long index;
|
|
|
|
u32 parent;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (nlmsg_len(cb->nlh) < sizeof(*tcm))
|
|
|
|
return skb->len;
|
|
|
|
|
netlink: make validation more configurable for future strictness
We currently have two levels of strict validation:
1) liberal (default)
- undefined (type >= max) & NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
- garbage at end of message accepted
2) strict (opt-in)
- NLA_UNSPEC attributes accepted
- attribute length >= expected accepted
Split out parsing strictness into four different options:
* TRAILING - check that there's no trailing data after parsing
attributes (in message or nested)
* MAXTYPE - reject attrs > max known type
* UNSPEC - reject attributes with NLA_UNSPEC policy entries
* STRICT_ATTRS - strictly validate attribute size
The default for future things should be *everything*.
The current *_strict() is a combination of TRAILING and MAXTYPE,
and is renamed to _deprecated_strict().
The current regular parsing has none of this, and is renamed to
*_parse_deprecated().
Additionally it allows us to selectively set one of the new flags
even on old policies. Notably, the UNSPEC flag could be useful in
this case, since it can be arranged (by filling in the policy) to
not be an incompatible userspace ABI change, but would then going
forward prevent forgetting attribute entries. Similar can apply
to the POLICY flag.
We end up with the following renames:
* nla_parse -> nla_parse_deprecated
* nla_parse_strict -> nla_parse_deprecated_strict
* nlmsg_parse -> nlmsg_parse_deprecated
* nlmsg_parse_strict -> nlmsg_parse_deprecated_strict
* nla_parse_nested -> nla_parse_nested_deprecated
* nla_validate_nested -> nla_validate_nested_deprecated
Using spatch, of course:
@@
expression TB, MAX, HEAD, LEN, POL, EXT;
@@
-nla_parse(TB, MAX, HEAD, LEN, POL, EXT)
+nla_parse_deprecated(TB, MAX, HEAD, LEN, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression NLH, HDRLEN, TB, MAX, POL, EXT;
@@
-nlmsg_parse_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
+nlmsg_parse_deprecated_strict(NLH, HDRLEN, TB, MAX, POL, EXT)
@@
expression TB, MAX, NLA, POL, EXT;
@@
-nla_parse_nested(TB, MAX, NLA, POL, EXT)
+nla_parse_nested_deprecated(TB, MAX, NLA, POL, EXT)
@@
expression START, MAX, POL, EXT;
@@
-nla_validate_nested(START, MAX, POL, EXT)
+nla_validate_nested_deprecated(START, MAX, POL, EXT)
@@
expression NLH, HDRLEN, MAX, POL, EXT;
@@
-nlmsg_validate(NLH, HDRLEN, MAX, POL, EXT)
+nlmsg_validate_deprecated(NLH, HDRLEN, MAX, POL, EXT)
For this patch, don't actually add the strict, non-renamed versions
yet so that it breaks compile if I get it wrong.
Also, while at it, make nla_validate and nla_parse go down to a
common __nla_validate_parse() function to avoid code duplication.
Ultimately, this allows us to have very strict validation for every
new caller of nla_parse()/nlmsg_parse() etc as re-introduced in the
next patch, while existing things will continue to work as is.
In effect then, this adds fully strict validation for any new command.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-26 19:07:28 +07:00
|
|
|
err = nlmsg_parse_deprecated(cb->nlh, sizeof(*tcm), tca, TCA_MAX,
|
|
|
|
rtm_tca_policy, cb->extack);
|
2018-07-23 14:23:06 +07:00
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (tcm->tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK) {
|
2018-09-24 23:22:58 +07:00
|
|
|
block = tcf_block_refcnt_get(net, tcm->tcm_block_index);
|
2018-07-23 14:23:06 +07:00
|
|
|
if (!block)
|
|
|
|
goto out;
|
|
|
|
/* If we work with block index, q is NULL and parent value
|
|
|
|
* will never be used in the following code. The check
|
|
|
|
* in tcf_fill_node prevents it. However, compiler does not
|
|
|
|
* see that far, so set parent to zero to silence the warning
|
|
|
|
* about parent being uninitialized.
|
|
|
|
*/
|
|
|
|
parent = 0;
|
|
|
|
} else {
|
|
|
|
const struct Qdisc_class_ops *cops;
|
|
|
|
struct net_device *dev;
|
|
|
|
unsigned long cl = 0;
|
|
|
|
|
|
|
|
dev = __dev_get_by_index(net, tcm->tcm_ifindex);
|
|
|
|
if (!dev)
|
|
|
|
return skb->len;
|
|
|
|
|
|
|
|
parent = tcm->tcm_parent;
|
|
|
|
if (!parent) {
|
|
|
|
q = dev->qdisc;
|
|
|
|
parent = q->handle;
|
|
|
|
} else {
|
|
|
|
q = qdisc_lookup(dev, TC_H_MAJ(tcm->tcm_parent));
|
|
|
|
}
|
|
|
|
if (!q)
|
|
|
|
goto out;
|
|
|
|
cops = q->ops->cl_ops;
|
|
|
|
if (!cops)
|
|
|
|
goto out;
|
|
|
|
if (!cops->tcf_block)
|
|
|
|
goto out;
|
|
|
|
if (TC_H_MIN(tcm->tcm_parent)) {
|
|
|
|
cl = cops->find(q, tcm->tcm_parent);
|
|
|
|
if (cl == 0)
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
block = cops->tcf_block(q, cl, NULL);
|
|
|
|
if (!block)
|
|
|
|
goto out;
|
|
|
|
if (tcf_block_shared(block))
|
|
|
|
q = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
index_start = cb->args[0];
|
|
|
|
index = 0;
|
|
|
|
|
2019-02-25 22:45:44 +07:00
|
|
|
mutex_lock(&block->lock);
|
|
|
|
list_for_each_entry(chain, &block->chain_list, list) {
|
2018-07-23 14:23:06 +07:00
|
|
|
if ((tca[TCA_CHAIN] &&
|
|
|
|
nla_get_u32(tca[TCA_CHAIN]) != chain->index))
|
|
|
|
continue;
|
|
|
|
if (index < index_start) {
|
|
|
|
index++;
|
|
|
|
continue;
|
|
|
|
}
|
2019-02-25 22:45:44 +07:00
|
|
|
if (tcf_chain_held_by_acts_only(chain))
|
|
|
|
continue;
|
2019-02-11 15:55:37 +07:00
|
|
|
err = tc_chain_fill_node(chain->tmplt_ops, chain->tmplt_priv,
|
|
|
|
chain->index, net, skb, block,
|
2018-07-23 14:23:06 +07:00
|
|
|
NETLINK_CB(cb->skb).portid,
|
|
|
|
cb->nlh->nlmsg_seq, NLM_F_MULTI,
|
|
|
|
RTM_NEWCHAIN);
|
2019-02-25 22:45:44 +07:00
|
|
|
if (err <= 0)
|
2018-07-23 14:23:06 +07:00
|
|
|
break;
|
|
|
|
index++;
|
|
|
|
}
|
2019-02-25 22:45:44 +07:00
|
|
|
mutex_unlock(&block->lock);
|
2018-07-23 14:23:06 +07:00
|
|
|
|
2018-09-24 23:22:58 +07:00
|
|
|
if (tcm->tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK)
|
2019-02-11 15:55:45 +07:00
|
|
|
tcf_block_refcnt_put(block, true);
|
2018-07-23 14:23:06 +07:00
|
|
|
cb->args[0] = index;
|
|
|
|
|
|
|
|
out:
|
|
|
|
/* If we did no progress, the error (EMSGSIZE) is real */
|
|
|
|
if (skb->len == 0 && err)
|
|
|
|
return err;
|
|
|
|
return skb->len;
|
|
|
|
}
|
|
|
|
|
2014-09-26 00:26:37 +07:00
|
|
|
void tcf_exts_destroy(struct tcf_exts *exts)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
#ifdef CONFIG_NET_CLS_ACT
|
2019-09-19 02:57:04 +07:00
|
|
|
if (exts->actions) {
|
|
|
|
tcf_action_destroy(exts->actions, TCA_ACT_UNBIND);
|
|
|
|
kfree(exts->actions);
|
|
|
|
}
|
2016-08-14 12:35:00 +07:00
|
|
|
exts->nr_actions = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
#endif
|
|
|
|
}
|
2008-01-21 17:26:41 +07:00
|
|
|
EXPORT_SYMBOL(tcf_exts_destroy);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-01-14 12:15:39 +07:00
|
|
|
int tcf_exts_validate(struct net *net, struct tcf_proto *tp, struct nlattr **tb,
|
2018-01-18 23:20:52 +07:00
|
|
|
struct nlattr *rate_tlv, struct tcf_exts *exts, bool ovr,
|
2019-02-11 15:55:43 +07:00
|
|
|
bool rtnl_held, struct netlink_ext_ack *extack)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
#ifdef CONFIG_NET_CLS_ACT
|
|
|
|
{
|
|
|
|
struct tc_action *act;
|
2018-03-09 04:59:17 +07:00
|
|
|
size_t attr_size = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-12-16 11:15:07 +07:00
|
|
|
if (exts->police && tb[exts->police]) {
|
2017-05-17 16:08:02 +07:00
|
|
|
act = tcf_action_init_1(net, tp, tb[exts->police],
|
|
|
|
rate_tlv, "police", ovr,
|
2019-02-11 15:55:43 +07:00
|
|
|
TCA_ACT_BIND, rtnl_held,
|
|
|
|
extack);
|
2008-01-24 11:33:13 +07:00
|
|
|
if (IS_ERR(act))
|
|
|
|
return PTR_ERR(act);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-12-16 11:15:05 +07:00
|
|
|
act->type = exts->type = TCA_OLD_COMPAT;
|
2016-08-14 12:35:00 +07:00
|
|
|
exts->actions[0] = act;
|
|
|
|
exts->nr_actions = 1;
|
2013-12-16 11:15:07 +07:00
|
|
|
} else if (exts->action && tb[exts->action]) {
|
2018-07-05 21:24:33 +07:00
|
|
|
int err;
|
2016-08-14 12:35:00 +07:00
|
|
|
|
2017-05-17 16:08:02 +07:00
|
|
|
err = tcf_action_init(net, tp, tb[exts->action],
|
|
|
|
rate_tlv, NULL, ovr, TCA_ACT_BIND,
|
2019-02-11 15:55:43 +07:00
|
|
|
exts->actions, &attr_size,
|
|
|
|
rtnl_held, extack);
|
2018-07-05 21:24:33 +07:00
|
|
|
if (err < 0)
|
2013-12-16 11:15:05 +07:00
|
|
|
return err;
|
2018-07-05 21:24:33 +07:00
|
|
|
exts->nr_actions = err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
2013-12-16 11:15:07 +07:00
|
|
|
if ((exts->action && tb[exts->action]) ||
|
2018-01-18 23:20:52 +07:00
|
|
|
(exts->police && tb[exts->police])) {
|
|
|
|
NL_SET_ERR_MSG(extack, "Classifier actions are not supported per compile options (CONFIG_NET_CLS_ACT)");
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EOPNOTSUPP;
|
2018-01-18 23:20:52 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2008-01-21 17:26:41 +07:00
|
|
|
EXPORT_SYMBOL(tcf_exts_validate);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2017-08-04 19:29:15 +07:00
|
|
|
void tcf_exts_change(struct tcf_exts *dst, struct tcf_exts *src)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
#ifdef CONFIG_NET_CLS_ACT
|
2016-08-14 12:35:00 +07:00
|
|
|
struct tcf_exts old = *dst;
|
|
|
|
|
2017-08-04 19:29:15 +07:00
|
|
|
*dst = *src;
|
2016-08-14 12:35:00 +07:00
|
|
|
tcf_exts_destroy(&old);
|
2005-04-17 05:20:36 +07:00
|
|
|
#endif
|
|
|
|
}
|
2008-01-21 17:26:41 +07:00
|
|
|
EXPORT_SYMBOL(tcf_exts_change);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2016-08-14 12:35:00 +07:00
|
|
|
#ifdef CONFIG_NET_CLS_ACT
|
|
|
|
static struct tc_action *tcf_exts_first_act(struct tcf_exts *exts)
|
|
|
|
{
|
|
|
|
if (exts->nr_actions == 0)
|
|
|
|
return NULL;
|
|
|
|
else
|
|
|
|
return exts->actions[0];
|
|
|
|
}
|
|
|
|
#endif
|
2013-12-16 11:15:05 +07:00
|
|
|
|
2013-12-16 11:15:07 +07:00
|
|
|
int tcf_exts_dump(struct sk_buff *skb, struct tcf_exts *exts)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
#ifdef CONFIG_NET_CLS_ACT
|
2014-07-17 04:25:30 +07:00
|
|
|
struct nlattr *nest;
|
|
|
|
|
2017-08-04 19:29:03 +07:00
|
|
|
if (exts->action && tcf_exts_has_actions(exts)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* again for backward compatible mode - we want
|
|
|
|
* to work with both old and new modes of entering
|
|
|
|
* tc data even if iproute2 was newer - jhs
|
|
|
|
*/
|
2013-12-16 11:15:05 +07:00
|
|
|
if (exts->type != TCA_OLD_COMPAT) {
|
2019-04-26 16:13:06 +07:00
|
|
|
nest = nla_nest_start_noflag(skb, exts->action);
|
2008-01-24 11:34:11 +07:00
|
|
|
if (nest == NULL)
|
|
|
|
goto nla_put_failure;
|
2016-08-14 12:35:00 +07:00
|
|
|
|
2018-07-05 21:24:33 +07:00
|
|
|
if (tcf_action_dump(skb, exts->actions, 0, 0) < 0)
|
2008-01-23 13:11:33 +07:00
|
|
|
goto nla_put_failure;
|
2008-01-24 11:34:11 +07:00
|
|
|
nla_nest_end(skb, nest);
|
2013-12-16 11:15:07 +07:00
|
|
|
} else if (exts->police) {
|
2013-12-16 11:15:05 +07:00
|
|
|
struct tc_action *act = tcf_exts_first_act(exts);
|
2019-04-26 16:13:06 +07:00
|
|
|
nest = nla_nest_start_noflag(skb, exts->police);
|
2013-12-23 20:02:12 +07:00
|
|
|
if (nest == NULL || !act)
|
2008-01-24 11:34:11 +07:00
|
|
|
goto nla_put_failure;
|
2013-12-16 11:15:05 +07:00
|
|
|
if (tcf_action_dump_old(skb, act, 0, 0) < 0)
|
2008-01-23 13:11:33 +07:00
|
|
|
goto nla_put_failure;
|
2008-01-24 11:34:11 +07:00
|
|
|
nla_nest_end(skb, nest);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
2014-07-17 04:25:30 +07:00
|
|
|
|
|
|
|
nla_put_failure:
|
|
|
|
nla_nest_cancel(skb, nest);
|
2005-04-17 05:20:36 +07:00
|
|
|
return -1;
|
2014-07-17 04:25:30 +07:00
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#endif
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2008-01-21 17:26:41 +07:00
|
|
|
EXPORT_SYMBOL(tcf_exts_dump);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-01-21 17:26:41 +07:00
|
|
|
|
2013-12-16 11:15:07 +07:00
|
|
|
int tcf_exts_dump_stats(struct sk_buff *skb, struct tcf_exts *exts)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
#ifdef CONFIG_NET_CLS_ACT
|
2013-12-16 11:15:05 +07:00
|
|
|
struct tc_action *a = tcf_exts_first_act(exts);
|
2015-02-04 01:05:18 +07:00
|
|
|
if (a != NULL && tcf_action_copy_stats(skb, a, 1) < 0)
|
2013-12-16 11:15:05 +07:00
|
|
|
return -1;
|
2005-04-17 05:20:36 +07:00
|
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
}
|
2008-01-21 17:26:41 +07:00
|
|
|
EXPORT_SYMBOL(tcf_exts_dump_stats);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2019-08-26 20:44:59 +07:00
|
|
|
static void tcf_block_offload_inc(struct tcf_block *block, u32 *flags)
|
|
|
|
{
|
|
|
|
if (*flags & TCA_CLS_FLAGS_IN_HW)
|
|
|
|
return;
|
|
|
|
*flags |= TCA_CLS_FLAGS_IN_HW;
|
|
|
|
atomic_inc(&block->offloadcnt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_block_offload_dec(struct tcf_block *block, u32 *flags)
|
|
|
|
{
|
|
|
|
if (!(*flags & TCA_CLS_FLAGS_IN_HW))
|
|
|
|
return;
|
|
|
|
*flags &= ~TCA_CLS_FLAGS_IN_HW;
|
|
|
|
atomic_dec(&block->offloadcnt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tc_cls_offload_cnt_update(struct tcf_block *block,
|
|
|
|
struct tcf_proto *tp, u32 *cnt,
|
|
|
|
u32 *flags, u32 diff, bool add)
|
|
|
|
{
|
|
|
|
lockdep_assert_held(&block->cb_lock);
|
|
|
|
|
|
|
|
spin_lock(&tp->lock);
|
|
|
|
if (add) {
|
|
|
|
if (!*cnt)
|
|
|
|
tcf_block_offload_inc(block, flags);
|
|
|
|
*cnt += diff;
|
|
|
|
} else {
|
|
|
|
*cnt -= diff;
|
|
|
|
if (!*cnt)
|
|
|
|
tcf_block_offload_dec(block, flags);
|
|
|
|
}
|
|
|
|
spin_unlock(&tp->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tc_cls_offload_cnt_reset(struct tcf_block *block, struct tcf_proto *tp,
|
|
|
|
u32 *cnt, u32 *flags)
|
|
|
|
{
|
|
|
|
lockdep_assert_held(&block->cb_lock);
|
|
|
|
|
|
|
|
spin_lock(&tp->lock);
|
|
|
|
tcf_block_offload_dec(block, flags);
|
|
|
|
*cnt = 0;
|
|
|
|
spin_unlock(&tp->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
__tc_setup_cb_call(struct tcf_block *block, enum tc_setup_type type,
|
|
|
|
void *type_data, bool err_stop)
|
2017-10-11 14:41:09 +07:00
|
|
|
{
|
2019-07-10 03:55:46 +07:00
|
|
|
struct flow_block_cb *block_cb;
|
2018-12-12 02:15:46 +07:00
|
|
|
int ok_count = 0;
|
|
|
|
int err;
|
|
|
|
|
2019-07-19 23:20:16 +07:00
|
|
|
list_for_each_entry(block_cb, &block->flow_block.cb_list, list) {
|
2018-12-12 02:15:46 +07:00
|
|
|
err = block_cb->cb(type, type_data, block_cb->cb_priv);
|
|
|
|
if (err) {
|
2019-08-26 20:44:59 +07:00
|
|
|
if (err_stop)
|
|
|
|
return err;
|
2018-12-12 02:15:46 +07:00
|
|
|
} else {
|
|
|
|
ok_count++;
|
|
|
|
}
|
|
|
|
}
|
2019-08-26 20:44:59 +07:00
|
|
|
return ok_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
int tc_setup_cb_call(struct tcf_block *block, enum tc_setup_type type,
|
|
|
|
void *type_data, bool err_stop, bool rtnl_held)
|
|
|
|
{
|
2019-08-26 20:45:02 +07:00
|
|
|
bool take_rtnl = READ_ONCE(block->lockeddevcnt) && !rtnl_held;
|
2019-08-26 20:44:59 +07:00
|
|
|
int ok_count;
|
|
|
|
|
2019-08-26 20:45:02 +07:00
|
|
|
retry:
|
|
|
|
if (take_rtnl)
|
|
|
|
rtnl_lock();
|
2019-08-26 20:44:59 +07:00
|
|
|
down_read(&block->cb_lock);
|
2019-08-26 20:45:02 +07:00
|
|
|
/* Need to obtain rtnl lock if block is bound to devs that require it.
|
|
|
|
* In block bind code cb_lock is obtained while holding rtnl, so we must
|
|
|
|
* obtain the locks in same order here.
|
|
|
|
*/
|
|
|
|
if (!rtnl_held && !take_rtnl && block->lockeddevcnt) {
|
|
|
|
up_read(&block->cb_lock);
|
|
|
|
take_rtnl = true;
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
|
2019-08-26 20:44:59 +07:00
|
|
|
ok_count = __tc_setup_cb_call(block, type, type_data, err_stop);
|
2019-08-26 20:45:02 +07:00
|
|
|
|
2019-08-26 20:44:57 +07:00
|
|
|
up_read(&block->cb_lock);
|
2019-08-26 20:45:02 +07:00
|
|
|
if (take_rtnl)
|
|
|
|
rtnl_unlock();
|
2018-12-12 02:15:46 +07:00
|
|
|
return ok_count;
|
2017-10-11 14:41:09 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tc_setup_cb_call);
|
2017-10-11 14:41:08 +07:00
|
|
|
|
2019-08-26 20:44:59 +07:00
|
|
|
/* Non-destructive filter add. If filter that wasn't already in hardware is
|
|
|
|
* successfully offloaded, increment block offloads counter. On failure,
|
|
|
|
* previously offloaded filter is considered to be intact and offloads counter
|
|
|
|
* is not decremented.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int tc_setup_cb_add(struct tcf_block *block, struct tcf_proto *tp,
|
|
|
|
enum tc_setup_type type, void *type_data, bool err_stop,
|
|
|
|
u32 *flags, unsigned int *in_hw_count, bool rtnl_held)
|
|
|
|
{
|
2019-08-26 20:45:02 +07:00
|
|
|
bool take_rtnl = READ_ONCE(block->lockeddevcnt) && !rtnl_held;
|
2019-08-26 20:44:59 +07:00
|
|
|
int ok_count;
|
|
|
|
|
2019-08-26 20:45:02 +07:00
|
|
|
retry:
|
|
|
|
if (take_rtnl)
|
|
|
|
rtnl_lock();
|
2019-08-26 20:44:59 +07:00
|
|
|
down_read(&block->cb_lock);
|
2019-08-26 20:45:02 +07:00
|
|
|
/* Need to obtain rtnl lock if block is bound to devs that require it.
|
|
|
|
* In block bind code cb_lock is obtained while holding rtnl, so we must
|
|
|
|
* obtain the locks in same order here.
|
|
|
|
*/
|
|
|
|
if (!rtnl_held && !take_rtnl && block->lockeddevcnt) {
|
|
|
|
up_read(&block->cb_lock);
|
|
|
|
take_rtnl = true;
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
|
2019-08-26 20:44:59 +07:00
|
|
|
/* Make sure all netdevs sharing this block are offload-capable. */
|
|
|
|
if (block->nooffloaddevcnt && err_stop) {
|
|
|
|
ok_count = -EOPNOTSUPP;
|
|
|
|
goto err_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
ok_count = __tc_setup_cb_call(block, type, type_data, err_stop);
|
2019-08-26 20:45:00 +07:00
|
|
|
if (ok_count < 0)
|
|
|
|
goto err_unlock;
|
|
|
|
|
|
|
|
if (tp->ops->hw_add)
|
|
|
|
tp->ops->hw_add(tp, type_data);
|
2019-08-26 20:44:59 +07:00
|
|
|
if (ok_count > 0)
|
|
|
|
tc_cls_offload_cnt_update(block, tp, in_hw_count, flags,
|
|
|
|
ok_count, true);
|
|
|
|
err_unlock:
|
|
|
|
up_read(&block->cb_lock);
|
2019-08-26 20:45:02 +07:00
|
|
|
if (take_rtnl)
|
|
|
|
rtnl_unlock();
|
2019-08-26 20:44:59 +07:00
|
|
|
return ok_count < 0 ? ok_count : 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tc_setup_cb_add);
|
|
|
|
|
|
|
|
/* Destructive filter replace. If filter that wasn't already in hardware is
|
|
|
|
* successfully offloaded, increment block offload counter. On failure,
|
|
|
|
* previously offloaded filter is considered to be destroyed and offload counter
|
|
|
|
* is decremented.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int tc_setup_cb_replace(struct tcf_block *block, struct tcf_proto *tp,
|
|
|
|
enum tc_setup_type type, void *type_data, bool err_stop,
|
|
|
|
u32 *old_flags, unsigned int *old_in_hw_count,
|
|
|
|
u32 *new_flags, unsigned int *new_in_hw_count,
|
|
|
|
bool rtnl_held)
|
|
|
|
{
|
2019-08-26 20:45:02 +07:00
|
|
|
bool take_rtnl = READ_ONCE(block->lockeddevcnt) && !rtnl_held;
|
2019-08-26 20:44:59 +07:00
|
|
|
int ok_count;
|
|
|
|
|
2019-08-26 20:45:02 +07:00
|
|
|
retry:
|
|
|
|
if (take_rtnl)
|
|
|
|
rtnl_lock();
|
2019-08-26 20:44:59 +07:00
|
|
|
down_read(&block->cb_lock);
|
2019-08-26 20:45:02 +07:00
|
|
|
/* Need to obtain rtnl lock if block is bound to devs that require it.
|
|
|
|
* In block bind code cb_lock is obtained while holding rtnl, so we must
|
|
|
|
* obtain the locks in same order here.
|
|
|
|
*/
|
|
|
|
if (!rtnl_held && !take_rtnl && block->lockeddevcnt) {
|
|
|
|
up_read(&block->cb_lock);
|
|
|
|
take_rtnl = true;
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
|
2019-08-26 20:44:59 +07:00
|
|
|
/* Make sure all netdevs sharing this block are offload-capable. */
|
|
|
|
if (block->nooffloaddevcnt && err_stop) {
|
|
|
|
ok_count = -EOPNOTSUPP;
|
|
|
|
goto err_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
tc_cls_offload_cnt_reset(block, tp, old_in_hw_count, old_flags);
|
2019-08-26 20:45:00 +07:00
|
|
|
if (tp->ops->hw_del)
|
|
|
|
tp->ops->hw_del(tp, type_data);
|
2019-08-26 20:44:59 +07:00
|
|
|
|
|
|
|
ok_count = __tc_setup_cb_call(block, type, type_data, err_stop);
|
2019-08-26 20:45:00 +07:00
|
|
|
if (ok_count < 0)
|
|
|
|
goto err_unlock;
|
|
|
|
|
|
|
|
if (tp->ops->hw_add)
|
|
|
|
tp->ops->hw_add(tp, type_data);
|
2019-08-26 20:44:59 +07:00
|
|
|
if (ok_count > 0)
|
2019-08-26 20:45:00 +07:00
|
|
|
tc_cls_offload_cnt_update(block, tp, new_in_hw_count,
|
|
|
|
new_flags, ok_count, true);
|
2019-08-26 20:44:59 +07:00
|
|
|
err_unlock:
|
|
|
|
up_read(&block->cb_lock);
|
2019-08-26 20:45:02 +07:00
|
|
|
if (take_rtnl)
|
|
|
|
rtnl_unlock();
|
2019-08-26 20:44:59 +07:00
|
|
|
return ok_count < 0 ? ok_count : 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tc_setup_cb_replace);
|
|
|
|
|
|
|
|
/* Destroy filter and decrement block offload counter, if filter was previously
|
|
|
|
* offloaded.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int tc_setup_cb_destroy(struct tcf_block *block, struct tcf_proto *tp,
|
|
|
|
enum tc_setup_type type, void *type_data, bool err_stop,
|
|
|
|
u32 *flags, unsigned int *in_hw_count, bool rtnl_held)
|
|
|
|
{
|
2019-08-26 20:45:02 +07:00
|
|
|
bool take_rtnl = READ_ONCE(block->lockeddevcnt) && !rtnl_held;
|
2019-08-26 20:44:59 +07:00
|
|
|
int ok_count;
|
|
|
|
|
2019-08-26 20:45:02 +07:00
|
|
|
retry:
|
|
|
|
if (take_rtnl)
|
|
|
|
rtnl_lock();
|
2019-08-26 20:44:59 +07:00
|
|
|
down_read(&block->cb_lock);
|
2019-08-26 20:45:02 +07:00
|
|
|
/* Need to obtain rtnl lock if block is bound to devs that require it.
|
|
|
|
* In block bind code cb_lock is obtained while holding rtnl, so we must
|
|
|
|
* obtain the locks in same order here.
|
|
|
|
*/
|
|
|
|
if (!rtnl_held && !take_rtnl && block->lockeddevcnt) {
|
|
|
|
up_read(&block->cb_lock);
|
|
|
|
take_rtnl = true;
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
|
2019-08-26 20:44:59 +07:00
|
|
|
ok_count = __tc_setup_cb_call(block, type, type_data, err_stop);
|
|
|
|
|
|
|
|
tc_cls_offload_cnt_reset(block, tp, in_hw_count, flags);
|
2019-08-26 20:45:00 +07:00
|
|
|
if (tp->ops->hw_del)
|
|
|
|
tp->ops->hw_del(tp, type_data);
|
|
|
|
|
2019-08-26 20:44:59 +07:00
|
|
|
up_read(&block->cb_lock);
|
2019-08-26 20:45:02 +07:00
|
|
|
if (take_rtnl)
|
|
|
|
rtnl_unlock();
|
2019-08-26 20:44:59 +07:00
|
|
|
return ok_count < 0 ? ok_count : 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tc_setup_cb_destroy);
|
|
|
|
|
|
|
|
int tc_setup_cb_reoffload(struct tcf_block *block, struct tcf_proto *tp,
|
|
|
|
bool add, flow_setup_cb_t *cb,
|
|
|
|
enum tc_setup_type type, void *type_data,
|
|
|
|
void *cb_priv, u32 *flags, unsigned int *in_hw_count)
|
|
|
|
{
|
|
|
|
int err = cb(type, type_data, cb_priv);
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
if (add && tc_skip_sw(*flags))
|
|
|
|
return err;
|
|
|
|
} else {
|
|
|
|
tc_cls_offload_cnt_update(block, tp, in_hw_count, flags, 1,
|
|
|
|
add);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tc_setup_cb_reoffload);
|
|
|
|
|
2020-02-25 17:45:18 +07:00
|
|
|
static int tcf_act_get_cookie(struct flow_action_entry *entry,
|
|
|
|
const struct tc_action *act)
|
|
|
|
{
|
|
|
|
struct tc_cookie *cookie;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
rcu_read_lock();
|
|
|
|
cookie = rcu_dereference(act->act_cookie);
|
|
|
|
if (cookie) {
|
|
|
|
entry->cookie = flow_action_cookie_create(cookie->data,
|
|
|
|
cookie->len,
|
|
|
|
GFP_ATOMIC);
|
|
|
|
if (!entry->cookie)
|
|
|
|
err = -ENOMEM;
|
|
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_act_put_cookie(struct flow_action_entry *entry)
|
|
|
|
{
|
|
|
|
flow_action_cookie_destroy(entry->cookie);
|
|
|
|
}
|
|
|
|
|
2019-08-26 20:45:04 +07:00
|
|
|
void tc_cleanup_flow_action(struct flow_action *flow_action)
|
|
|
|
{
|
|
|
|
struct flow_action_entry *entry;
|
|
|
|
int i;
|
|
|
|
|
2020-02-25 17:45:18 +07:00
|
|
|
flow_action_for_each(i, entry, flow_action) {
|
|
|
|
tcf_act_put_cookie(entry);
|
2019-09-13 22:28:39 +07:00
|
|
|
if (entry->destructor)
|
|
|
|
entry->destructor(entry->destructor_priv);
|
2020-02-25 17:45:18 +07:00
|
|
|
}
|
2019-08-26 20:45:04 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tc_cleanup_flow_action);
|
|
|
|
|
2019-09-13 22:28:39 +07:00
|
|
|
static void tcf_mirred_get_dev(struct flow_action_entry *entry,
|
|
|
|
const struct tc_action *act)
|
|
|
|
{
|
2019-09-13 22:28:41 +07:00
|
|
|
#ifdef CONFIG_NET_CLS_ACT
|
|
|
|
entry->dev = act->ops->get_dev(act, &entry->destructor);
|
2019-09-13 22:28:39 +07:00
|
|
|
if (!entry->dev)
|
|
|
|
return;
|
|
|
|
entry->destructor_priv = entry->dev;
|
2019-09-13 22:28:41 +07:00
|
|
|
#endif
|
2019-09-13 22:28:39 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void tcf_tunnel_encap_put_tunnel(void *priv)
|
|
|
|
{
|
|
|
|
struct ip_tunnel_info *tunnel = priv;
|
|
|
|
|
|
|
|
kfree(tunnel);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int tcf_tunnel_encap_get_tunnel(struct flow_action_entry *entry,
|
|
|
|
const struct tc_action *act)
|
|
|
|
{
|
|
|
|
entry->tunnel = tcf_tunnel_info_copy(act);
|
|
|
|
if (!entry->tunnel)
|
|
|
|
return -ENOMEM;
|
|
|
|
entry->destructor = tcf_tunnel_encap_put_tunnel;
|
|
|
|
entry->destructor_priv = entry->tunnel;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-09-13 22:28:40 +07:00
|
|
|
static void tcf_sample_get_group(struct flow_action_entry *entry,
|
|
|
|
const struct tc_action *act)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_NET_CLS_ACT
|
|
|
|
entry->sample.psample_group =
|
|
|
|
act->ops->get_psample_group(act, &entry->destructor);
|
|
|
|
entry->destructor_priv = entry->sample.psample_group;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-05-01 07:53:16 +07:00
|
|
|
static void tcf_gate_entry_destructor(void *priv)
|
|
|
|
{
|
|
|
|
struct action_gate_entry *oe = priv;
|
|
|
|
|
|
|
|
kfree(oe);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int tcf_gate_get_entries(struct flow_action_entry *entry,
|
|
|
|
const struct tc_action *act)
|
|
|
|
{
|
|
|
|
entry->gate.entries = tcf_gate_get_list(act);
|
|
|
|
|
|
|
|
if (!entry->gate.entries)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
entry->destructor = tcf_gate_entry_destructor;
|
|
|
|
entry->destructor_priv = entry->gate.entries;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-02-02 18:50:46 +07:00
|
|
|
int tc_setup_flow_action(struct flow_action *flow_action,
|
2020-02-17 17:12:12 +07:00
|
|
|
const struct tcf_exts *exts)
|
2019-02-02 18:50:46 +07:00
|
|
|
{
|
2020-02-17 17:12:09 +07:00
|
|
|
struct tc_action *act;
|
2019-08-26 20:45:03 +07:00
|
|
|
int i, j, k, err = 0;
|
2019-02-02 18:50:46 +07:00
|
|
|
|
2020-03-20 06:26:23 +07:00
|
|
|
BUILD_BUG_ON(TCA_ACT_HW_STATS_ANY != FLOW_ACTION_HW_STATS_ANY);
|
|
|
|
BUILD_BUG_ON(TCA_ACT_HW_STATS_IMMEDIATE != FLOW_ACTION_HW_STATS_IMMEDIATE);
|
|
|
|
BUILD_BUG_ON(TCA_ACT_HW_STATS_DELAYED != FLOW_ACTION_HW_STATS_DELAYED);
|
2020-03-07 18:40:20 +07:00
|
|
|
|
2019-02-02 18:50:46 +07:00
|
|
|
if (!exts)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
j = 0;
|
|
|
|
tcf_exts_for_each_action(i, act, exts) {
|
|
|
|
struct flow_action_entry *entry;
|
|
|
|
|
|
|
|
entry = &flow_action->entries[j];
|
2020-02-17 17:12:09 +07:00
|
|
|
spin_lock_bh(&act->tcfa_lock);
|
2020-02-25 17:45:18 +07:00
|
|
|
err = tcf_act_get_cookie(entry, act);
|
|
|
|
if (err)
|
|
|
|
goto err_out_locked;
|
2020-03-07 18:40:20 +07:00
|
|
|
|
2020-03-20 06:26:23 +07:00
|
|
|
entry->hw_stats = act->hw_stats;
|
2020-03-07 18:40:20 +07:00
|
|
|
|
2019-02-02 18:50:46 +07:00
|
|
|
if (is_tcf_gact_ok(act)) {
|
|
|
|
entry->id = FLOW_ACTION_ACCEPT;
|
|
|
|
} else if (is_tcf_gact_shot(act)) {
|
|
|
|
entry->id = FLOW_ACTION_DROP;
|
|
|
|
} else if (is_tcf_gact_trap(act)) {
|
|
|
|
entry->id = FLOW_ACTION_TRAP;
|
|
|
|
} else if (is_tcf_gact_goto_chain(act)) {
|
|
|
|
entry->id = FLOW_ACTION_GOTO;
|
|
|
|
entry->chain_index = tcf_gact_goto_chain_index(act);
|
|
|
|
} else if (is_tcf_mirred_egress_redirect(act)) {
|
|
|
|
entry->id = FLOW_ACTION_REDIRECT;
|
2019-09-13 22:28:39 +07:00
|
|
|
tcf_mirred_get_dev(entry, act);
|
2019-02-02 18:50:46 +07:00
|
|
|
} else if (is_tcf_mirred_egress_mirror(act)) {
|
|
|
|
entry->id = FLOW_ACTION_MIRRED;
|
2019-09-13 22:28:39 +07:00
|
|
|
tcf_mirred_get_dev(entry, act);
|
2019-08-04 22:09:06 +07:00
|
|
|
} else if (is_tcf_mirred_ingress_redirect(act)) {
|
|
|
|
entry->id = FLOW_ACTION_REDIRECT_INGRESS;
|
2019-09-13 22:28:39 +07:00
|
|
|
tcf_mirred_get_dev(entry, act);
|
2019-08-04 22:09:06 +07:00
|
|
|
} else if (is_tcf_mirred_ingress_mirror(act)) {
|
|
|
|
entry->id = FLOW_ACTION_MIRRED_INGRESS;
|
2019-09-13 22:28:39 +07:00
|
|
|
tcf_mirred_get_dev(entry, act);
|
2019-02-02 18:50:46 +07:00
|
|
|
} else if (is_tcf_vlan(act)) {
|
|
|
|
switch (tcf_vlan_action(act)) {
|
|
|
|
case TCA_VLAN_ACT_PUSH:
|
|
|
|
entry->id = FLOW_ACTION_VLAN_PUSH;
|
|
|
|
entry->vlan.vid = tcf_vlan_push_vid(act);
|
|
|
|
entry->vlan.proto = tcf_vlan_push_proto(act);
|
|
|
|
entry->vlan.prio = tcf_vlan_push_prio(act);
|
|
|
|
break;
|
|
|
|
case TCA_VLAN_ACT_POP:
|
|
|
|
entry->id = FLOW_ACTION_VLAN_POP;
|
|
|
|
break;
|
|
|
|
case TCA_VLAN_ACT_MODIFY:
|
|
|
|
entry->id = FLOW_ACTION_VLAN_MANGLE;
|
|
|
|
entry->vlan.vid = tcf_vlan_push_vid(act);
|
|
|
|
entry->vlan.proto = tcf_vlan_push_proto(act);
|
|
|
|
entry->vlan.prio = tcf_vlan_push_prio(act);
|
|
|
|
break;
|
|
|
|
default:
|
2019-08-26 20:45:03 +07:00
|
|
|
err = -EOPNOTSUPP;
|
2020-02-17 17:12:09 +07:00
|
|
|
goto err_out_locked;
|
2019-02-02 18:50:46 +07:00
|
|
|
}
|
|
|
|
} else if (is_tcf_tunnel_set(act)) {
|
|
|
|
entry->id = FLOW_ACTION_TUNNEL_ENCAP;
|
2019-09-13 22:28:39 +07:00
|
|
|
err = tcf_tunnel_encap_get_tunnel(entry, act);
|
|
|
|
if (err)
|
2020-02-17 17:12:09 +07:00
|
|
|
goto err_out_locked;
|
2019-02-02 18:50:46 +07:00
|
|
|
} else if (is_tcf_tunnel_release(act)) {
|
|
|
|
entry->id = FLOW_ACTION_TUNNEL_DECAP;
|
|
|
|
} else if (is_tcf_pedit(act)) {
|
|
|
|
for (k = 0; k < tcf_pedit_nkeys(act); k++) {
|
|
|
|
switch (tcf_pedit_cmd(act, k)) {
|
|
|
|
case TCA_PEDIT_KEY_EX_CMD_SET:
|
|
|
|
entry->id = FLOW_ACTION_MANGLE;
|
|
|
|
break;
|
|
|
|
case TCA_PEDIT_KEY_EX_CMD_ADD:
|
|
|
|
entry->id = FLOW_ACTION_ADD;
|
|
|
|
break;
|
|
|
|
default:
|
2019-08-26 20:45:03 +07:00
|
|
|
err = -EOPNOTSUPP;
|
2020-02-17 17:12:09 +07:00
|
|
|
goto err_out_locked;
|
2019-02-02 18:50:46 +07:00
|
|
|
}
|
|
|
|
entry->mangle.htype = tcf_pedit_htype(act, k);
|
|
|
|
entry->mangle.mask = tcf_pedit_mask(act, k);
|
|
|
|
entry->mangle.val = tcf_pedit_val(act, k);
|
|
|
|
entry->mangle.offset = tcf_pedit_offset(act, k);
|
2020-03-20 06:26:23 +07:00
|
|
|
entry->hw_stats = act->hw_stats;
|
2020-03-19 00:42:29 +07:00
|
|
|
entry = &flow_action->entries[++j];
|
2019-02-02 18:50:46 +07:00
|
|
|
}
|
|
|
|
} else if (is_tcf_csum(act)) {
|
|
|
|
entry->id = FLOW_ACTION_CSUM;
|
|
|
|
entry->csum_flags = tcf_csum_update_flags(act);
|
|
|
|
} else if (is_tcf_skbedit_mark(act)) {
|
|
|
|
entry->id = FLOW_ACTION_MARK;
|
|
|
|
entry->mark = tcf_skbedit_mark(act);
|
2019-05-04 18:46:16 +07:00
|
|
|
} else if (is_tcf_sample(act)) {
|
|
|
|
entry->id = FLOW_ACTION_SAMPLE;
|
|
|
|
entry->sample.trunc_size = tcf_sample_trunc_size(act);
|
|
|
|
entry->sample.truncate = tcf_sample_truncate(act);
|
|
|
|
entry->sample.rate = tcf_sample_rate(act);
|
2019-09-13 22:28:40 +07:00
|
|
|
tcf_sample_get_group(entry, act);
|
2019-05-04 18:46:22 +07:00
|
|
|
} else if (is_tcf_police(act)) {
|
|
|
|
entry->id = FLOW_ACTION_POLICE;
|
|
|
|
entry->police.burst = tcf_police_tcfp_burst(act);
|
|
|
|
entry->police.rate_bytes_ps =
|
|
|
|
tcf_police_rate_bytes_ps(act);
|
2019-07-09 14:30:48 +07:00
|
|
|
} else if (is_tcf_ct(act)) {
|
|
|
|
entry->id = FLOW_ACTION_CT;
|
|
|
|
entry->ct.action = tcf_ct_action(act);
|
|
|
|
entry->ct.zone = tcf_ct_zone(act);
|
2020-03-12 17:23:09 +07:00
|
|
|
entry->ct.flow_table = tcf_ct_ft(act);
|
2019-07-23 21:33:59 +07:00
|
|
|
} else if (is_tcf_mpls(act)) {
|
|
|
|
switch (tcf_mpls_action(act)) {
|
|
|
|
case TCA_MPLS_ACT_PUSH:
|
|
|
|
entry->id = FLOW_ACTION_MPLS_PUSH;
|
|
|
|
entry->mpls_push.proto = tcf_mpls_proto(act);
|
|
|
|
entry->mpls_push.label = tcf_mpls_label(act);
|
|
|
|
entry->mpls_push.tc = tcf_mpls_tc(act);
|
|
|
|
entry->mpls_push.bos = tcf_mpls_bos(act);
|
|
|
|
entry->mpls_push.ttl = tcf_mpls_ttl(act);
|
|
|
|
break;
|
|
|
|
case TCA_MPLS_ACT_POP:
|
|
|
|
entry->id = FLOW_ACTION_MPLS_POP;
|
|
|
|
entry->mpls_pop.proto = tcf_mpls_proto(act);
|
|
|
|
break;
|
|
|
|
case TCA_MPLS_ACT_MODIFY:
|
|
|
|
entry->id = FLOW_ACTION_MPLS_MANGLE;
|
|
|
|
entry->mpls_mangle.label = tcf_mpls_label(act);
|
|
|
|
entry->mpls_mangle.tc = tcf_mpls_tc(act);
|
|
|
|
entry->mpls_mangle.bos = tcf_mpls_bos(act);
|
|
|
|
entry->mpls_mangle.ttl = tcf_mpls_ttl(act);
|
|
|
|
break;
|
|
|
|
default:
|
2020-02-17 17:12:09 +07:00
|
|
|
goto err_out_locked;
|
2019-07-23 21:33:59 +07:00
|
|
|
}
|
2019-08-04 22:09:04 +07:00
|
|
|
} else if (is_tcf_skbedit_ptype(act)) {
|
|
|
|
entry->id = FLOW_ACTION_PTYPE;
|
|
|
|
entry->ptype = tcf_skbedit_ptype(act);
|
2020-03-19 20:47:21 +07:00
|
|
|
} else if (is_tcf_skbedit_priority(act)) {
|
|
|
|
entry->id = FLOW_ACTION_PRIORITY;
|
|
|
|
entry->priority = tcf_skbedit_priority(act);
|
2020-05-01 07:53:16 +07:00
|
|
|
} else if (is_tcf_gate(act)) {
|
|
|
|
entry->id = FLOW_ACTION_GATE;
|
|
|
|
entry->gate.index = tcf_gate_index(act);
|
|
|
|
entry->gate.prio = tcf_gate_prio(act);
|
|
|
|
entry->gate.basetime = tcf_gate_basetime(act);
|
|
|
|
entry->gate.cycletime = tcf_gate_cycletime(act);
|
|
|
|
entry->gate.cycletimeext = tcf_gate_cycletimeext(act);
|
|
|
|
entry->gate.num_entries = tcf_gate_num_entries(act);
|
|
|
|
err = tcf_gate_get_entries(entry, act);
|
|
|
|
if (err)
|
|
|
|
goto err_out;
|
2019-02-02 18:50:46 +07:00
|
|
|
} else {
|
2019-08-26 20:45:03 +07:00
|
|
|
err = -EOPNOTSUPP;
|
2020-02-17 17:12:09 +07:00
|
|
|
goto err_out_locked;
|
2019-02-02 18:50:46 +07:00
|
|
|
}
|
2020-02-17 17:12:09 +07:00
|
|
|
spin_unlock_bh(&act->tcfa_lock);
|
2019-02-02 18:50:46 +07:00
|
|
|
|
|
|
|
if (!is_tcf_pedit(act))
|
|
|
|
j++;
|
|
|
|
}
|
2019-08-26 20:45:03 +07:00
|
|
|
|
2019-02-02 18:50:46 +07:00
|
|
|
err_out:
|
2019-08-26 20:45:04 +07:00
|
|
|
if (err)
|
|
|
|
tc_cleanup_flow_action(flow_action);
|
|
|
|
|
2019-08-26 20:45:03 +07:00
|
|
|
return err;
|
2020-02-17 17:12:09 +07:00
|
|
|
err_out_locked:
|
|
|
|
spin_unlock_bh(&act->tcfa_lock);
|
|
|
|
goto err_out;
|
2019-02-02 18:50:46 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tc_setup_flow_action);
|
|
|
|
|
2019-02-02 18:50:45 +07:00
|
|
|
unsigned int tcf_exts_num_actions(struct tcf_exts *exts)
|
|
|
|
{
|
|
|
|
unsigned int num_acts = 0;
|
|
|
|
struct tc_action *act;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
tcf_exts_for_each_action(i, act, exts) {
|
|
|
|
if (is_tcf_pedit(act))
|
|
|
|
num_acts += tcf_pedit_nkeys(act);
|
|
|
|
else
|
|
|
|
num_acts++;
|
|
|
|
}
|
|
|
|
return num_acts;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(tcf_exts_num_actions);
|
|
|
|
|
2018-01-17 17:46:46 +07:00
|
|
|
static __net_init int tcf_net_init(struct net *net)
|
|
|
|
{
|
|
|
|
struct tcf_net *tn = net_generic(net, tcf_net_id);
|
|
|
|
|
2018-09-24 23:22:56 +07:00
|
|
|
spin_lock_init(&tn->idr_lock);
|
2018-01-17 17:46:46 +07:00
|
|
|
idr_init(&tn->idr);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __net_exit tcf_net_exit(struct net *net)
|
|
|
|
{
|
|
|
|
struct tcf_net *tn = net_generic(net, tcf_net_id);
|
|
|
|
|
|
|
|
idr_destroy(&tn->idr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct pernet_operations tcf_net_ops = {
|
|
|
|
.init = tcf_net_init,
|
|
|
|
.exit = tcf_net_exit,
|
|
|
|
.id = &tcf_net_id,
|
|
|
|
.size = sizeof(struct tcf_net),
|
|
|
|
};
|
|
|
|
|
2019-12-06 00:03:35 +07:00
|
|
|
static struct flow_indr_block_entry block_entry = {
|
|
|
|
.cb = tc_indr_block_get_and_cmd,
|
|
|
|
.list = LIST_HEAD_INIT(block_entry.list),
|
2019-08-07 08:13:53 +07:00
|
|
|
};
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static int __init tc_filter_init(void)
|
|
|
|
{
|
2018-01-17 17:46:46 +07:00
|
|
|
int err;
|
|
|
|
|
2017-10-27 08:24:28 +07:00
|
|
|
tc_filter_wq = alloc_ordered_workqueue("tc_filter_workqueue", 0);
|
|
|
|
if (!tc_filter_wq)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2018-01-17 17:46:46 +07:00
|
|
|
err = register_pernet_subsys(&tcf_net_ops);
|
|
|
|
if (err)
|
|
|
|
goto err_register_pernet_subsys;
|
|
|
|
|
2019-12-06 00:03:35 +07:00
|
|
|
flow_indr_add_block_cb(&block_entry);
|
2019-08-07 08:13:53 +07:00
|
|
|
|
2019-02-11 15:55:48 +07:00
|
|
|
rtnl_register(PF_UNSPEC, RTM_NEWTFILTER, tc_new_tfilter, NULL,
|
|
|
|
RTNL_FLAG_DOIT_UNLOCKED);
|
|
|
|
rtnl_register(PF_UNSPEC, RTM_DELTFILTER, tc_del_tfilter, NULL,
|
|
|
|
RTNL_FLAG_DOIT_UNLOCKED);
|
2018-05-31 13:52:53 +07:00
|
|
|
rtnl_register(PF_UNSPEC, RTM_GETTFILTER, tc_get_tfilter,
|
2019-02-11 15:55:48 +07:00
|
|
|
tc_dump_tfilter, RTNL_FLAG_DOIT_UNLOCKED);
|
2018-07-23 14:23:06 +07:00
|
|
|
rtnl_register(PF_UNSPEC, RTM_NEWCHAIN, tc_ctl_chain, NULL, 0);
|
|
|
|
rtnl_register(PF_UNSPEC, RTM_DELCHAIN, tc_ctl_chain, NULL, 0);
|
|
|
|
rtnl_register(PF_UNSPEC, RTM_GETCHAIN, tc_ctl_chain,
|
|
|
|
tc_dump_chain, 0);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
return 0;
|
2018-01-17 17:46:46 +07:00
|
|
|
|
|
|
|
err_register_pernet_subsys:
|
|
|
|
destroy_workqueue(tc_filter_wq);
|
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
subsys_initcall(tc_filter_init);
|