Commit ef718bc3 authored by David S. Miller's avatar David S. Miller

Merge branch 'classifier-no-rtnl'

Vlad Buslov says:

====================
Refactor classifier API to work with chain/classifiers without rtnl lock

Currently, all netlink protocol handlers for updating rules, actions and
qdiscs are protected with single global rtnl lock which removes any
possibility for parallelism. This patch set is a third step to remove
rtnl lock dependency from TC rules update path.

Recently, new rtnl registration flag RTNL_FLAG_DOIT_UNLOCKED was added.
Handlers registered with this flag are called without RTNL taken. End
goal is to have rule update handlers(RTM_NEWTFILTER, RTM_DELTFILTER,
etc.) to be registered with UNLOCKED flag to allow parallel execution.
However, there is no intention to completely remove or split rtnl lock
itself. This patch set addresses specific problems in implementation of
classifiers API that prevent its control path from being executed
concurrently, and completes refactoring of cls API rules update handlers
by removing rtnl lock dependency from code that handles chains and
classifiers. Rules update handlers are registered with
RTNL_FLAG_DOIT_UNLOCKED flag.

This patch set substitutes global rtnl lock dependency on rules update
path in cls API by extending its data structures with following locks:
- tcf_block with 'lock' mutex. It is used to protect block state and
  life-time management fields of chains on the block (chain->refcnt,
  chain->action_refcnt, chain->explicitly crated, etc.).
- tcf_chain with 'filter_chain_lock' mutex, that is used to protect list
  of classifier instances attached to chain. chain0->filter_chain_lock
  serializes calls to head change callbacks and allows them to rely on
  filter_chain_lock for serialization instead of rtnl lock.
- tcf_proto with 'lock' spinlock that is intended to be used to
  synchronize access to classifiers that support unlocked execution.

Classifiers are extended with reference counting to accommodate parallel
access by unlocked cls API. Classifier ops structure is extended with
additional 'put' function to allow reference counting of filters and
intended to be used by classifiers that implement rtnl-unlocked API.
Users of classifiers and individual filter instances are modified to
always hold reference while working with them.

Classifiers that support unlocked execution still need to know the
status of rtnl lock, so their API is extended with additional
'rtnl_held' argument that is used to indicate that caller holds rtnl
lock. Cls API propagates rtnl lock status across its helper functions
and passes it to classifier.

Changes from V3 to V4:
- Patch 1:
  - Extract code that manages chain 'explicitly_created' flag into
    standalone patch.
- Patch 2 - new.

Changes from V2 to V3:
- Change block->lock and chain->filter_chain_lock type to mutex. This
  removes the need for async miniqp refactoring and allows calling
  sleeping functions while holding the block->lock and
  chain->filter_chain_lock locks.
- Previous patch 1 - async miniqp is no longer needed, remove the patch.
- Patch 1:
  - Change block->lock type to mutex.
  - Implement tcf_block_destroy() helper function that destroys
    block->lock mutex before deallocating the block.
  - Revert GFP_KERNEL->GFP_ATOMIC memory allocation flags of tcf_chain
    which is no longer needed after block->lock type change.
- Patch 6:
  - Change chain->filter_chain_lock type to mutex.
  - Assume chain0->filter_chain_lock synchronizations instead of rtnl
    lock in mini_qdisc_pair_swap() function that is called from head
    change callback of ingress Qdisc. With filter_chain_lock type
    changed to mutex it is now possible to call sleeping function while
    holding it, so it is now used instead of async implementation from
    previous versions of this patch set.
- Patch 7:
  - Add local tp_next var to tcf_chain_flush() and use it to store
    tp->next pointer dereferenced with rcu_dereference_protected() to
    satisfy kbuild test robot.
  - Reset tp pointer to NULL at the beginning of tc_new_tfilter() to
    prevent its uninitialized usage in error handling code. This code
    was already implemented in patch 10, but must be in patch 8 to
    preserve code bisectability.
  - Put parent chain in tcf_proto_destroy(). In previous version this
    code was implemented in patch 1 which was removed in V3.

Changes from V1 to V2:
- Patch 1:
  - Use smp_store_release() instead of xchg() for setting
    miniqp->tp_head.
  - Move Qdisc deallocation to tc_proto_wq ordered workqueue that is
    used to destroy tcf proto instances. This is necessary to ensure
    that Qdisc is destroyed after all instances of chain/proto that it
    contains in order to prevent use-after-free error in
    tc_chain_notify_delete().
  - Cache parent net device ifindex in block to prevent use-after-free
    of dev queue in tc_chain_notify_delete().
- Patch 2:
  - Use lockdep_assert_held() instead of spin_is_locked() for assertion.
  - Use 'free_block' argument in tcf_chain_destroy() instead of checking
    block's reference count and chain_list for second time.
- Patch 7:
  - Refactor tcf_chain0_head_change_cb_add() to not take block->lock and
    chain0->filter_chain_lock in correct order.
- Patch 10:
  - Always set 'tp_created' flag when creating tp to prevent releasing
    the chain twice when tp with same priority was inserted
    concurrently.
- Patch 11:
  - Add additional check to prevent creation of new proto instance when
    parent chain is being flushed to reduce CPU usage.
  - Don't call tcf_chain_delete_empty() if tp insertion failed.
- Patch 16 - new.
- Patch 17:
  - Refactor to only lock take rtnl lock once (at the beginning of rule
    update handlers).
  - Always release rtnl mutex in the same function that locked it.
    Remove unlock code from tcf_block_release().
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents b67de691 470502de
......@@ -44,6 +44,10 @@ bool tcf_queue_work(struct rcu_work *rwork, work_func_t func);
struct tcf_chain *tcf_chain_get_by_act(struct tcf_block *block,
u32 chain_index);
void tcf_chain_put_by_act(struct tcf_chain *chain);
struct tcf_chain *tcf_get_next_chain(struct tcf_block *block,
struct tcf_chain *chain);
struct tcf_proto *tcf_get_next_proto(struct tcf_chain *chain,
struct tcf_proto *tp, bool rtnl_held);
void tcf_block_netif_keep_dst(struct tcf_block *block);
int tcf_block_get(struct tcf_block **p_block,
struct tcf_proto __rcu **p_filter_chain, struct Qdisc *q,
......@@ -412,7 +416,7 @@ tcf_exts_exec(struct sk_buff *skb, struct tcf_exts *exts,
int tcf_exts_validate(struct net *net, struct tcf_proto *tp,
struct nlattr **tb, struct nlattr *rate_tlv,
struct tcf_exts *exts, bool ovr,
struct tcf_exts *exts, bool ovr, bool rtnl_held,
struct netlink_ext_ack *extack);
void tcf_exts_destroy(struct tcf_exts *exts);
void tcf_exts_change(struct tcf_exts *dst, struct tcf_exts *src);
......
......@@ -12,6 +12,7 @@
#include <linux/list.h>
#include <linux/refcount.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <net/gen_stats.h>
#include <net/rtnetlink.h>
......@@ -178,6 +179,7 @@ static inline int qdisc_avail_bulklimit(const struct netdev_queue *txq)
}
struct Qdisc_class_ops {
unsigned int flags;
/* Child qdisc manipulation */
struct netdev_queue * (*select_queue)(struct Qdisc *, struct tcmsg *);
int (*graft)(struct Qdisc *, unsigned long cl,
......@@ -209,6 +211,13 @@ struct Qdisc_class_ops {
struct gnet_dump *);
};
/* Qdisc_class_ops flag values */
/* Implements API that doesn't require rtnl lock */
enum qdisc_class_ops_flags {
QDISC_CLASS_OPS_DOIT_UNLOCKED = 1,
};
struct Qdisc_ops {
struct Qdisc_ops *next;
const struct Qdisc_class_ops *cl_ops;
......@@ -272,19 +281,21 @@ struct tcf_proto_ops {
const struct tcf_proto *,
struct tcf_result *);
int (*init)(struct tcf_proto*);
void (*destroy)(struct tcf_proto *tp,
void (*destroy)(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack);
void* (*get)(struct tcf_proto*, u32 handle);
void (*put)(struct tcf_proto *tp, void *f);
int (*change)(struct net *net, struct sk_buff *,
struct tcf_proto*, unsigned long,
u32 handle, struct nlattr **,
void **, bool,
void **, bool, bool,
struct netlink_ext_ack *);
int (*delete)(struct tcf_proto *tp, void *arg,
bool *last,
bool *last, bool rtnl_held,
struct netlink_ext_ack *);
void (*walk)(struct tcf_proto*, struct tcf_walker *arg);
void (*walk)(struct tcf_proto *tp,
struct tcf_walker *arg, bool rtnl_held);
int (*reoffload)(struct tcf_proto *tp, bool add,
tc_setup_cb_t *cb, void *cb_priv,
struct netlink_ext_ack *extack);
......@@ -297,12 +308,18 @@ struct tcf_proto_ops {
/* rtnetlink specific */
int (*dump)(struct net*, struct tcf_proto*, void *,
struct sk_buff *skb, struct tcmsg*);
struct sk_buff *skb, struct tcmsg*,
bool);
int (*tmplt_dump)(struct sk_buff *skb,
struct net *net,
void *tmplt_priv);
struct module *owner;
int flags;
};
enum tcf_proto_ops_flags {
TCF_PROTO_OPS_DOIT_UNLOCKED = 1,
};
struct tcf_proto {
......@@ -321,6 +338,12 @@ struct tcf_proto {
void *data;
const struct tcf_proto_ops *ops;
struct tcf_chain *chain;
/* Lock protects tcf_proto shared state and can be used by unlocked
* classifiers to protect their private data.
*/
spinlock_t lock;
bool deleting;
refcount_t refcnt;
struct rcu_head rcu;
};
......@@ -340,6 +363,8 @@ struct qdisc_skb_cb {
typedef void tcf_chain_head_change_t(struct tcf_proto *tp_head, void *priv);
struct tcf_chain {
/* Protects filter_chain. */
struct mutex filter_chain_lock;
struct tcf_proto __rcu *filter_chain;
struct list_head list;
struct tcf_block *block;
......@@ -347,11 +372,16 @@ struct tcf_chain {
unsigned int refcnt;
unsigned int action_refcnt;
bool explicitly_created;
bool flushing;
const struct tcf_proto_ops *tmplt_ops;
void *tmplt_priv;
};
struct tcf_block {
/* Lock protects tcf_block and lifetime-management data of chains
* attached to the block (refcnt, action_refcnt, explicitly_created).
*/
struct mutex lock;
struct list_head chain_list;
u32 index; /* block index for shared blocks */
refcount_t refcnt;
......@@ -369,6 +399,34 @@ struct tcf_block {
struct rcu_head rcu;
};
#ifdef CONFIG_PROVE_LOCKING
static inline bool lockdep_tcf_chain_is_locked(struct tcf_chain *chain)
{
return lockdep_is_held(&chain->filter_chain_lock);
}
static inline bool lockdep_tcf_proto_is_locked(struct tcf_proto *tp)
{
return lockdep_is_held(&tp->lock);
}
#else
static inline bool lockdep_tcf_chain_is_locked(struct tcf_block *chain)
{
return true;
}
static inline bool lockdep_tcf_proto_is_locked(struct tcf_proto *tp)
{
return true;
}
#endif /* #ifdef CONFIG_PROVE_LOCKING */
#define tcf_chain_dereference(p, chain) \
rcu_dereference_protected(p, lockdep_tcf_chain_is_locked(chain))
#define tcf_proto_dereference(p, tp) \
rcu_dereference_protected(p, lockdep_tcf_proto_is_locked(tp))
static inline void tcf_block_offload_inc(struct tcf_block *block, u32 *flags)
{
if (*flags & TCA_CLS_FLAGS_IN_HW)
......
......@@ -69,7 +69,8 @@ static const struct tcf_proto_ops *__tcf_proto_lookup_ops(const char *kind)
}
static const struct tcf_proto_ops *
tcf_proto_lookup_ops(const char *kind, struct netlink_ext_ack *extack)
tcf_proto_lookup_ops(const char *kind, bool rtnl_held,
struct netlink_ext_ack *extack)
{
const struct tcf_proto_ops *ops;
......@@ -77,9 +78,11 @@ tcf_proto_lookup_ops(const char *kind, struct netlink_ext_ack *extack)
if (ops)
return ops;
#ifdef CONFIG_MODULES
rtnl_unlock();
if (rtnl_held)
rtnl_unlock();
request_module("cls_%s", kind);
rtnl_lock();
if (rtnl_held)
rtnl_lock();
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
......@@ -160,8 +163,26 @@ static inline u32 tcf_auto_prio(struct tcf_proto *tp)
return TC_H_MAJ(first);
}
static bool tcf_proto_is_unlocked(const char *kind)
{
const struct tcf_proto_ops *ops;
bool ret;
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;
}
static struct tcf_proto *tcf_proto_create(const char *kind, u32 protocol,
u32 prio, struct tcf_chain *chain,
bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct tcf_proto *tp;
......@@ -171,7 +192,7 @@ static struct tcf_proto *tcf_proto_create(const char *kind, u32 protocol,
if (!tp)
return ERR_PTR(-ENOBUFS);
tp->ops = tcf_proto_lookup_ops(kind, extack);
tp->ops = tcf_proto_lookup_ops(kind, rtnl_held, extack);
if (IS_ERR(tp->ops)) {
err = PTR_ERR(tp->ops);
goto errout;
......@@ -180,6 +201,8 @@ static struct tcf_proto *tcf_proto_create(const char *kind, u32 protocol,
tp->protocol = protocol;
tp->prio = prio;
tp->chain = chain;
spin_lock_init(&tp->lock);
refcount_set(&tp->refcnt, 1);
err = tp->ops->init(tp);
if (err) {
......@@ -193,14 +216,75 @@ static struct tcf_proto *tcf_proto_create(const char *kind, u32 protocol,
return ERR_PTR(err);
}
static void tcf_proto_destroy(struct tcf_proto *tp,
static void tcf_proto_get(struct tcf_proto *tp)
{
refcount_inc(&tp->refcnt);
}
static void tcf_chain_put(struct tcf_chain *chain);
static void tcf_proto_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
tp->ops->destroy(tp, extack);
tp->ops->destroy(tp, rtnl_held, extack);
tcf_chain_put(tp->chain);
module_put(tp->ops->owner);
kfree_rcu(tp, rcu);
}
static void tcf_proto_put(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
if (refcount_dec_and_test(&tp->refcnt))
tcf_proto_destroy(tp, rtnl_held, extack);
}
static int walker_noop(struct tcf_proto *tp, void *d, struct tcf_walker *arg)
{
return -1;
}
static bool tcf_proto_is_empty(struct tcf_proto *tp, bool rtnl_held)
{
struct tcf_walker walker = { .fn = walker_noop, };
if (tp->ops->walk) {
tp->ops->walk(tp, &walker, rtnl_held);
return !walker.stop;
}
return true;
}
static bool tcf_proto_check_delete(struct tcf_proto *tp, bool rtnl_held)
{
spin_lock(&tp->lock);
if (tcf_proto_is_empty(tp, rtnl_held))
tp->deleting = true;
spin_unlock(&tp->lock);
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;
}
#define ASSERT_BLOCK_LOCKED(block) \
lockdep_assert_held(&(block)->lock)
struct tcf_filter_chain_list_item {
struct list_head list;
tcf_chain_head_change_t *chain_head_change;
......@@ -212,10 +296,13 @@ static struct tcf_chain *tcf_chain_create(struct tcf_block *block,
{
struct tcf_chain *chain;
ASSERT_BLOCK_LOCKED(block);
chain = kzalloc(sizeof(*chain), GFP_KERNEL);
if (!chain)
return NULL;
list_add_tail(&chain->list, &block->chain_list);
mutex_init(&chain->filter_chain_lock);
chain->block = block;
chain->index = chain_index;
chain->refcnt = 1;
......@@ -239,29 +326,59 @@ static void tcf_chain0_head_change(struct tcf_chain *chain,
if (chain->index)
return;
mutex_lock(&block->lock);
list_for_each_entry(item, &block->chain0.filter_chain_list, list)
tcf_chain_head_change_item(item, tp_head);
mutex_unlock(&block->lock);
}
static void tcf_chain_destroy(struct tcf_chain *chain)
/* Returns true if block can be safely freed. */
static bool tcf_chain_detach(struct tcf_chain *chain)
{
struct tcf_block *block = chain->block;
ASSERT_BLOCK_LOCKED(block);
list_del(&chain->list);
if (!chain->index)
block->chain0.chain = NULL;
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);
kfree_rcu(block, rcu);
}
static void tcf_chain_destroy(struct tcf_chain *chain, bool free_block)
{
struct tcf_block *block = chain->block;
mutex_destroy(&chain->filter_chain_lock);
kfree(chain);
if (list_empty(&block->chain_list) && !refcount_read(&block->refcnt))
kfree_rcu(block, rcu);
if (free_block)
tcf_block_destroy(block);
}
static void tcf_chain_hold(struct tcf_chain *chain)
{
ASSERT_BLOCK_LOCKED(chain->block);
++chain->refcnt;
}
static bool tcf_chain_held_by_acts_only(struct tcf_chain *chain)
{
ASSERT_BLOCK_LOCKED(chain->block);
/* In case all the references are action references, this
* chain should not be shown to the user.
*/
......@@ -273,6 +390,8 @@ static struct tcf_chain *tcf_chain_lookup(struct tcf_block *block,
{
struct tcf_chain *chain;
ASSERT_BLOCK_LOCKED(block);
list_for_each_entry(chain, &block->chain_list, list) {
if (chain->index == chain_index)
return chain;
......@@ -287,31 +406,40 @@ static struct tcf_chain *__tcf_chain_get(struct tcf_block *block,
u32 chain_index, bool create,
bool by_act)
{
struct tcf_chain *chain = tcf_chain_lookup(block, chain_index);
struct tcf_chain *chain = NULL;
bool is_first_reference;
mutex_lock(&block->lock);
chain = tcf_chain_lookup(block, chain_index);
if (chain) {
tcf_chain_hold(chain);
} else {
if (!create)
return NULL;
goto errout;
chain = tcf_chain_create(block, chain_index);
if (!chain)
return NULL;
goto errout;
}
if (by_act)
++chain->action_refcnt;
is_first_reference = chain->refcnt - chain->action_refcnt == 1;
mutex_unlock(&block->lock);
/* 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.
*/
if (chain->refcnt - chain->action_refcnt == 1 && !by_act)
if (is_first_reference && !by_act)
tc_chain_notify(chain, NULL, 0, NLM_F_CREATE | NLM_F_EXCL,
RTM_NEWCHAIN, false);
return chain;
errout:
mutex_unlock(&block->lock);
return chain;
}
static struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index,
......@@ -326,51 +454,94 @@ struct tcf_chain *tcf_chain_get_by_act(struct tcf_block *block, u32 chain_index)
}
EXPORT_SYMBOL(tcf_chain_get_by_act);
static void tc_chain_tmplt_del(struct tcf_chain *chain);
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);
static void __tcf_chain_put(struct tcf_chain *chain, bool by_act)
static void __tcf_chain_put(struct tcf_chain *chain, bool by_act,
bool explicitly_created)
{
struct tcf_block *block = chain->block;
const struct tcf_proto_ops *tmplt_ops;
bool is_last, free_block = false;
unsigned int refcnt;
void *tmplt_priv;
u32 chain_index;
mutex_lock(&block->lock);
if (explicitly_created) {
if (!chain->explicitly_created) {
mutex_unlock(&block->lock);
return;
}
chain->explicitly_created = false;
}
if (by_act)
chain->action_refcnt--;
chain->refcnt--;
/* 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;
is_last = refcnt - chain->action_refcnt == 0;
tmplt_ops = chain->tmplt_ops;
tmplt_priv = chain->tmplt_priv;
chain_index = chain->index;
if (refcnt == 0)
free_block = tcf_chain_detach(chain);
mutex_unlock(&block->lock);
/* The last dropped non-action reference will trigger notification. */
if (chain->refcnt - chain->action_refcnt == 0 && !by_act)
tc_chain_notify(chain, NULL, 0, 0, RTM_DELCHAIN, false);
if (is_last && !by_act) {
tc_chain_notify_delete(tmplt_ops, tmplt_priv, chain_index,
block, NULL, 0, 0, false);
/* Last reference to chain, no need to lock. */
chain->flushing = false;
}
if (chain->refcnt == 0) {
tc_chain_tmplt_del(chain);
tcf_chain_destroy(chain);
if (refcnt == 0) {
tc_chain_tmplt_del(tmplt_ops, tmplt_priv);
tcf_chain_destroy(chain, free_block);
}
}
static void tcf_chain_put(struct tcf_chain *chain)
{
__tcf_chain_put(chain, false);
__tcf_chain_put(chain, false, false);
}
void tcf_chain_put_by_act(struct tcf_chain *chain)
{
__tcf_chain_put(chain, true);
__tcf_chain_put(chain, true, false);
}
EXPORT_SYMBOL(tcf_chain_put_by_act);
static void tcf_chain_put_explicitly_created(struct tcf_chain *chain)
{
if (chain->explicitly_created)
tcf_chain_put(chain);
__tcf_chain_put(chain, false, true);
}
static void tcf_chain_flush(struct tcf_chain *chain)
static void tcf_chain_flush(struct tcf_chain *chain, bool rtnl_held)
{
struct tcf_proto *tp = rtnl_dereference(chain->filter_chain);
struct tcf_proto *tp, *tp_next;
mutex_lock(&chain->filter_chain_lock);
tp = tcf_chain_dereference(chain->filter_chain, chain);
RCU_INIT_POINTER(chain->filter_chain, NULL);
tcf_chain0_head_change(chain, NULL);
chain->flushing = true;
mutex_unlock(&chain->filter_chain_lock);
while (tp) {
RCU_INIT_POINTER(chain->filter_chain, tp->next);
tcf_proto_destroy(tp, NULL);
tp = rtnl_dereference(chain->filter_chain);
tcf_chain_put(chain);
tp_next = rcu_dereference_protected(tp->next, 1);
tcf_proto_put(tp, rtnl_held, NULL);
tp = tp_next;
}
}
......@@ -692,8 +863,8 @@ tcf_chain0_head_change_cb_add(struct tcf_block *block,
struct tcf_block_ext_info *ei,
struct netlink_ext_ack *extack)
{
struct tcf_chain *chain0 = block->chain0.chain;
struct tcf_filter_chain_list_item *item;
struct tcf_chain *chain0;
item = kmalloc(sizeof(*item), GFP_KERNEL);
if (!item) {
......@@ -702,9 +873,32 @@ tcf_chain0_head_change_cb_add(struct tcf_block *block,
}
item->chain_head_change = ei->chain_head_change;
item->chain_head_change_priv = ei->chain_head_change_priv;
if (chain0 && chain0->filter_chain)
tcf_chain_head_change_item(item, chain0->filter_chain);
list_add(&item->list, &block->chain0.filter_chain_list);
mutex_lock(&block->lock);
chain0 = block->chain0.chain;
if (chain0)
tcf_chain_hold(chain0);
else
list_add(&item->list, &block->chain0.filter_chain_list);
mutex_unlock(&block->lock);
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);
}
return 0;
}
......@@ -712,20 +906,23 @@ static void
tcf_chain0_head_change_cb_del(struct tcf_block *block,
struct tcf_block_ext_info *ei)
{
struct tcf_chain *chain0 = block->chain0.chain;
struct tcf_filter_chain_list_item *item;
mutex_lock(&block->lock);
list_for_each_entry(item, &block->chain0.filter_chain_list, list) {
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)) {
if (chain0)
if (block->chain0.chain)
tcf_chain_head_change_item(item, NULL);
list_del(&item->list);
mutex_unlock(&block->lock);
kfree(item);
return;
}
}
mutex_unlock(&block->lock);
WARN_ON(1);
}
......@@ -772,6 +969,7 @@ static struct tcf_block *tcf_block_create(struct net *net, struct Qdisc *q,
NL_SET_ERR_MSG(extack, "Memory allocation for block failed");
return ERR_PTR(-ENOMEM);
}
mutex_init(&block->lock);
INIT_LIST_HEAD(&block->chain_list);
INIT_LIST_HEAD(&block->cb_list);
INIT_LIST_HEAD(&block->owner_list);
......@@ -807,157 +1005,241 @@ static struct tcf_block *tcf_block_refcnt_get(struct net *net, u32 block_index)
return block;
}
static void tcf_block_flush_all_chains(struct tcf_block *block)
static struct tcf_chain *
__tcf_get_next_chain(struct tcf_block *block, struct tcf_chain *chain)
{
struct tcf_chain *chain;
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);
/* Hold a refcnt for all chains, so that they don't disappear
* while we are iterating.
*/
list_for_each_entry(chain, &block->chain_list, list)
/* 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)
tcf_chain_hold(chain);
mutex_unlock(&block->lock);
list_for_each_entry(chain, &block->chain_list, list)
tcf_chain_flush(chain);
return chain;
}
static void tcf_block_put_all_chains(struct tcf_block *block)
/* 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)
{
struct tcf_chain *chain, *tmp;
struct tcf_chain *chain_next = __tcf_get_next_chain(block, chain);
/* At this point, all the chains should have refcnt >= 1. */
list_for_each_entry_safe(chain, tmp, &block->chain_list, list) {
tcf_chain_put_explicitly_created(chain);
if (chain)
tcf_chain_put(chain);
}
return chain_next;
}
EXPORT_SYMBOL(tcf_get_next_chain);
static void __tcf_block_put(struct tcf_block *block, struct Qdisc *q,
struct tcf_block_ext_info *ei)
static struct tcf_proto *
__tcf_get_next_proto(struct tcf_chain *chain, struct tcf_proto *tp)
{
if (refcount_dec_and_test(&block->refcnt)) {
/* 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);
u32 prio = 0;
if (tcf_block_shared(block))
tcf_block_remove(block, block->net);
if (!free_block)
tcf_block_flush_all_chains(block);
ASSERT_RTNL();
mutex_lock(&chain->filter_chain_lock);
if (q)
tcf_block_offload_unbind(block, q, ei);
if (!tp) {
tp = tcf_chain_dereference(chain->filter_chain, chain);
} 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);
if (free_block)
kfree_rcu(block, rcu);
else
tcf_block_put_all_chains(block);
} else if (q) {
tcf_block_offload_unbind(block, q, ei);
for (; tp; tp = tcf_chain_dereference(tp->next, chain))
if (!tp->deleting && tp->prio >= prio)
break;
} else {
tp = tcf_chain_dereference(tp->next, chain);
}
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 *
tcf_get_next_proto(struct tcf_chain *chain, struct tcf_proto *tp,
bool rtnl_held)
{
struct tcf_proto *tp_next = __tcf_get_next_proto(chain, tp);
if (tp)
tcf_proto_put(tp, rtnl_held, NULL);
return tp_next;
}
EXPORT_SYMBOL(tcf_get_next_proto);
static void tcf_block_refcnt_put(struct tcf_block *block)
static void tcf_block_flush_all_chains(struct tcf_block *block, bool rtnl_held)
{
__tcf_block_put(block, NULL, NULL);
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);
tcf_chain_flush(chain, rtnl_held);
}
}
/* Find tcf block.
* Set q, parent, cl when appropriate.
/* Lookup Qdisc and increments its reference counter.
* Set parent, if necessary.
*/
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)
static int __tcf_qdisc_find(struct net *net, struct Qdisc **q,
u32 *parent, int ifindex, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct tcf_block *block;
const struct Qdisc_class_ops *cops;
struct net_device *dev;
int err = 0;
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;
struct net_device *dev;
rcu_read_lock();
if (ifindex == TCM_IFINDEX_MAGIC_BLOCK)
return 0;
/* Find link */
dev = dev_get_by_index_rcu(net, ifindex);
if (!dev) {
rcu_read_unlock();
return ERR_PTR(-ENODEV);
}
rcu_read_lock();
/* 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;
}
}
/* Find link */
dev = dev_get_by_index_rcu(net, ifindex);
if (!dev) {
rcu_read_unlock();
return -ENODEV;
}
*q = qdisc_refcount_inc_nz(*q);
/* 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;
}
}
/* Is it classful? */
cops = (*q)->ops->cl_ops;
if (!cops) {
NL_SET_ERR_MSG(extack, "Qdisc not classful");
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;
}
if (!cops->tcf_block) {
NL_SET_ERR_MSG(extack, "Class doesn't support blocks");
err = -EOPNOTSUPP;
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;
}
/* 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();
if (!cops->tcf_block) {
NL_SET_ERR_MSG(extack, "Class doesn't support blocks");
err = -EOPNOTSUPP;
goto errout_qdisc;
}
/* Do we search for filter, attached to class? */
if (TC_H_MIN(*parent)) {
*cl = cops->find(*q, *parent);
if (*cl == 0) {
NL_SET_ERR_MSG(extack, "Specified class doesn't exist");
err = -ENOENT;
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;
}
/* And the last stroke */
block = cops->tcf_block(*q, *cl, extack);
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) {
err = -EINVAL;
goto errout_qdisc;
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");
err = -EOPNOTSUPP;
goto errout_qdisc;
return ERR_PTR(-EOPNOTSUPP);
}
/* Always take reference to block in order to support execution
......@@ -970,24 +1252,89 @@ static struct tcf_block *tcf_block_find(struct net *net, struct Qdisc **q,
}
return block;
}
static void __tcf_block_put(struct tcf_block *block, struct Qdisc *q,
struct tcf_block_ext_info *ei, bool rtnl_held)
{
if (refcount_dec_and_mutex_lock(&block->refcnt, &block->lock)) {
/* 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);
mutex_unlock(&block->lock);
if (tcf_block_shared(block))
tcf_block_remove(block, block->net);
if (q)
tcf_block_offload_unbind(block, q, ei);
if (free_block)
tcf_block_destroy(block);
else
tcf_block_flush_all_chains(block, rtnl_held);
} else if (q) {
tcf_block_offload_unbind(block, q, ei);
}
}
static void tcf_block_refcnt_put(struct tcf_block *block, bool rtnl_held)
{
__tcf_block_put(block, NULL, NULL, rtnl_held);
}
/* 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;
int err = 0;
ASSERT_RTNL();
err = __tcf_qdisc_find(net, q, parent, ifindex, true, extack);
if (err)
goto errout;
err = __tcf_qdisc_cl_find(*q, *parent, cl, ifindex, extack);
if (err)
goto errout_qdisc;
block = __tcf_block_find(net, *q, *cl, ifindex, block_index, extack);
if (IS_ERR(block))
goto errout_qdisc;
return block;
errout_rcu:
rcu_read_unlock();
errout_qdisc:
if (*q) {
if (*q)
qdisc_put(*q);
*q = NULL;
}
errout:
*q = NULL;
return ERR_PTR(err);
}
static void tcf_block_release(struct Qdisc *q, struct tcf_block *block)
static void tcf_block_release(struct Qdisc *q, struct tcf_block *block,
bool rtnl_held)
{
if (!IS_ERR_OR_NULL(block))
tcf_block_refcnt_put(block);
tcf_block_refcnt_put(block, rtnl_held);
if (q)
qdisc_put(q);
if (q) {
if (rtnl_held)
qdisc_put(q);
else
qdisc_put_unlocked(q);
}
}
struct tcf_block_owner_item {
......@@ -1095,7 +1442,7 @@ int tcf_block_get_ext(struct tcf_block **p_block, struct Qdisc *q,
tcf_block_owner_del(block, q, ei->binder_type);
err_block_owner_add:
err_block_insert:
tcf_block_refcnt_put(block);
tcf_block_refcnt_put(block, true);
return err;
}
EXPORT_SYMBOL(tcf_block_get_ext);
......@@ -1132,7 +1479,7 @@ void tcf_block_put_ext(struct tcf_block *block, struct Qdisc *q,
tcf_chain0_head_change_cb_del(block, ei);
tcf_block_owner_del(block, q, ei->binder_type);
__tcf_block_put(block, q, ei);
__tcf_block_put(block, q, ei, true);
}
EXPORT_SYMBOL(tcf_block_put_ext);
......@@ -1189,13 +1536,19 @@ tcf_block_playback_offloads(struct tcf_block *block, tc_setup_cb_t *cb,
void *cb_priv, bool add, bool offload_in_use,
struct netlink_ext_ack *extack)
{
struct tcf_chain *chain;
struct tcf_proto *tp;
struct tcf_chain *chain, *chain_prev;
struct tcf_proto *tp, *tp_prev;
int err;
list_for_each_entry(chain, &block->chain_list, list) {
for (tp = rtnl_dereference(chain->filter_chain); tp;
tp = rtnl_dereference(tp->next)) {
for (chain = __tcf_get_next_chain(block, NULL);
chain;
chain_prev = chain,
chain = __tcf_get_next_chain(block, chain),
tcf_chain_put(chain_prev)) {
for (tp = __tcf_get_next_proto(chain, NULL); tp;
tp_prev = tp,
tp = __tcf_get_next_proto(chain, tp),
tcf_proto_put(tp_prev, true, NULL)) {
if (tp->ops->reoffload) {
err = tp->ops->reoffload(tp, add, cb, cb_priv,
extack);
......@@ -1212,6 +1565,8 @@ tcf_block_playback_offloads(struct tcf_block *block, tc_setup_cb_t *cb,
return 0;
err_playback_remove:
tcf_proto_put(tp, true, NULL);
tcf_chain_put(chain);
tcf_block_playback_offloads(block, cb, cb_priv, false, offload_in_use,
extack);
return err;
......@@ -1337,32 +1692,116 @@ struct tcf_chain_info {
struct tcf_proto __rcu *next;
};
static struct tcf_proto *tcf_chain_tp_prev(struct tcf_chain_info *chain_info)
static struct tcf_proto *tcf_chain_tp_prev(struct tcf_chain *chain,
struct tcf_chain_info *chain_info)
{
return rtnl_dereference(*chain_info->pprev);
return tcf_chain_dereference(*chain_info->pprev, chain);
}
static void tcf_chain_tp_insert(struct tcf_chain *chain,
struct tcf_chain_info *chain_info,
struct tcf_proto *tp)
static int tcf_chain_tp_insert(struct tcf_chain *chain,
struct tcf_chain_info *chain_info,
struct tcf_proto *tp)
{
if (chain->flushing)
return -EAGAIN;
if (*chain_info->pprev == chain->filter_chain)
tcf_chain0_head_change(chain, tp);
RCU_INIT_POINTER(tp->next, tcf_chain_tp_prev(chain_info));
tcf_proto_get(tp);
RCU_INIT_POINTER(tp->next, tcf_chain_tp_prev(chain, chain_info));
rcu_assign_pointer(*chain_info->pprev, tp);
tcf_chain_hold(chain);
return 0;
}
static void tcf_chain_tp_remove(struct tcf_chain *chain,
struct tcf_chain_info *chain_info,
struct tcf_proto *tp)
{
struct tcf_proto *next = rtnl_dereference(chain_info->next);
struct tcf_proto *next = tcf_chain_dereference(chain_info->next, chain);
tcf_proto_mark_delete(tp);
if (tp == chain->filter_chain)
tcf_chain0_head_change(chain, next);
RCU_INIT_POINTER(*chain_info->pprev, next);
tcf_chain_put(chain);
}
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,
u32 protocol, u32 prio,
bool rtnl_held)
{
struct tcf_chain_info chain_info;
struct tcf_proto *tp;
int err = 0;
mutex_lock(&chain->filter_chain_lock);
tp = tcf_chain_tp_find(chain, &chain_info,
protocol, prio, false);
if (!tp)
err = tcf_chain_tp_insert(chain, &chain_info, tp_new);
mutex_unlock(&chain->filter_chain_lock);
if (tp) {
tcf_proto_destroy(tp_new, rtnl_held, NULL);
tp_new = tp;
} else if (err) {
tcf_proto_destroy(tp_new, rtnl_held, NULL);
tp_new = ERR_PTR(err);
}
return tp_new;
}
static void tcf_chain_tp_delete_empty(struct tcf_chain *chain,
struct tcf_proto *tp, bool rtnl_held,
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.
*/
if (!tp_iter || !tcf_proto_check_delete(tp, rtnl_held)) {
mutex_unlock(&chain->filter_chain_lock);
return;
}
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);
tcf_proto_put(tp, rtnl_held, extack);
}
static struct tcf_proto *tcf_chain_tp_find(struct tcf_chain *chain,
......@@ -1375,7 +1814,8 @@ static struct tcf_proto *tcf_chain_tp_find(struct tcf_chain *chain,
/* Check the chain for existence of proto-tcf with this priority */
for (pprev = &chain->filter_chain;
(tp = rtnl_dereference(*pprev)); pprev = &tp->next) {
(tp = tcf_chain_dereference(*pprev, chain));
pprev = &tp->next) {
if (tp->prio >= prio) {
if (tp->prio == prio) {
if (prio_allocate ||
......@@ -1388,14 +1828,20 @@ static struct tcf_proto *tcf_chain_tp_find(struct tcf_chain *chain,
}
}
chain_info->pprev = pprev;
chain_info->next = tp ? tp->next : NULL;
if (tp) {
chain_info->next = tp->next;
tcf_proto_get(tp);
} else {
chain_info->next = NULL;
}
return tp;
}
static int tcf_fill_node(struct net *net, struct sk_buff *skb,
struct tcf_proto *tp, struct tcf_block *block,
struct Qdisc *q, u32 parent, void *fh,
u32 portid, u32 seq, u16 flags, int event)
u32 portid, u32 seq, u16 flags, int event,
bool rtnl_held)
{
struct tcmsg *tcm;
struct nlmsghdr *nlh;
......@@ -1423,7 +1869,8 @@ static int tcf_fill_node(struct net *net, struct sk_buff *skb,
if (!fh) {
tcm->tcm_handle = 0;
} else {
if (tp->ops->dump && tp->ops->dump(net, tp, fh, skb, tcm) < 0)
if (tp->ops->dump &&
tp->ops->dump(net, tp, fh, skb, tcm, rtnl_held) < 0)
goto nla_put_failure;
}
nlh->nlmsg_len = skb_tail_pointer(skb) - b;
......@@ -1438,7 +1885,8 @@ static int tcf_fill_node(struct net *net, struct sk_buff *skb,
static int tfilter_notify(struct net *net, struct sk_buff *oskb,
struct nlmsghdr *n, struct tcf_proto *tp,
struct tcf_block *block, struct Qdisc *q,
u32 parent, void *fh, int event, bool unicast)
u32 parent, void *fh, int event, bool unicast,
bool rtnl_held)
{
struct sk_buff *skb;
u32 portid = oskb ? NETLINK_CB(oskb).portid : 0;
......@@ -1448,7 +1896,8 @@ static int tfilter_notify(struct net *net, struct sk_buff *oskb,
return -ENOBUFS;
if (tcf_fill_node(net, skb, tp, block, q, parent, fh, portid,
n->nlmsg_seq, n->nlmsg_flags, event) <= 0) {
n->nlmsg_seq, n->nlmsg_flags, event,
rtnl_held) <= 0) {
kfree_skb(skb);
return -EINVAL;
}
......@@ -1464,7 +1913,7 @@ static int tfilter_del_notify(struct net *net, struct sk_buff *oskb,
struct nlmsghdr *n, struct tcf_proto *tp,
struct tcf_block *block, struct Qdisc *q,
u32 parent, void *fh, bool unicast, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct sk_buff *skb;
u32 portid = oskb ? NETLINK_CB(oskb).portid : 0;
......@@ -1475,13 +1924,14 @@ static int tfilter_del_notify(struct net *net, struct sk_buff *oskb,
return -ENOBUFS;
if (tcf_fill_node(net, skb, tp, block, q, parent, fh, portid,
n->nlmsg_seq, n->nlmsg_flags, RTM_DELTFILTER) <= 0) {
n->nlmsg_seq, n->nlmsg_flags, RTM_DELTFILTER,
rtnl_held) <= 0) {
NL_SET_ERR_MSG(extack, "Failed to build del event notification");
kfree_skb(skb);
return -EINVAL;
}
err = tp->ops->delete(tp, fh, last, extack);
err = tp->ops->delete(tp, fh, last, rtnl_held, extack);
if (err) {
kfree_skb(skb);
return err;
......@@ -1500,14 +1950,21 @@ static int tfilter_del_notify(struct net *net, struct sk_buff *oskb,
static void tfilter_notify_chain(struct net *net, struct sk_buff *oskb,
struct tcf_block *block, struct Qdisc *q,
u32 parent, struct nlmsghdr *n,
struct tcf_chain *chain, int event)
struct tcf_chain *chain, int event,
bool rtnl_held)
{
struct tcf_proto *tp;
for (tp = rtnl_dereference(chain->filter_chain);
tp; tp = rtnl_dereference(tp->next))
for (tp = tcf_get_next_proto(chain, NULL, rtnl_held);
tp; tp = tcf_get_next_proto(chain, tp, rtnl_held))
tfilter_notify(net, oskb, n, tp, block,
q, parent, NULL, event, false);
q, parent, NULL, event, false, rtnl_held);
}
static void tfilter_put(struct tcf_proto *tp, void *fh)
{
if (tp->ops->put && fh)
tp->ops->put(tp, fh);
}
static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
......@@ -1530,6 +1987,7 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
void *fh;
int err;
int tp_created;
bool rtnl_held = false;
if (!netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN))
return -EPERM;
......@@ -1546,7 +2004,9 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
prio = TC_H_MAJ(t->tcm_info);
prio_allocate = false;
parent = t->tcm_parent;
tp = NULL;
cl = 0;
block = NULL;
if (prio == 0) {
/* If no priority is provided by the user,
......@@ -1563,8 +2023,27 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
/* Find head of filter chain. */
block = tcf_block_find(net, &q, &parent, &cl,
t->tcm_ifindex, t->tcm_block_index, extack);
err = __tcf_qdisc_find(net, &q, &parent, t->tcm_ifindex, false, extack);
if (err)
return err;
/* 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)) ||
!tca[TCA_KIND] || !tcf_proto_is_unlocked(nla_data(tca[TCA_KIND]))) {
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);
if (IS_ERR(block)) {
err = PTR_ERR(block);
goto errout;
......@@ -1583,40 +2062,62 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
goto errout;
}
mutex_lock(&chain->filter_chain_lock);
tp = tcf_chain_tp_find(chain, &chain_info, protocol,
prio, prio_allocate);
if (IS_ERR(tp)) {
NL_SET_ERR_MSG(extack, "Filter with specified priority/protocol not found");
err = PTR_ERR(tp);
goto errout;
goto errout_locked;
}
if (tp == NULL) {
struct tcf_proto *tp_new = NULL;
if (chain->flushing) {
err = -EAGAIN;
goto errout_locked;
}
/* Proto-tcf does not exist, create new one */
if (tca[TCA_KIND] == NULL || !protocol) {
NL_SET_ERR_MSG(extack, "Filter kind and protocol must be specified");
err = -EINVAL;
goto errout;
goto errout_locked;
}
if (!(n->nlmsg_flags & NLM_F_CREATE)) {
NL_SET_ERR_MSG(extack, "Need both RTM_NEWTFILTER and NLM_F_CREATE to create a new filter");
err = -ENOENT;
goto errout;
goto errout_locked;
}
if (prio_allocate)
prio = tcf_auto_prio(tcf_chain_tp_prev(&chain_info));
prio = tcf_auto_prio(tcf_chain_tp_prev(chain,
&chain_info));
mutex_unlock(&chain->filter_chain_lock);
tp_new = tcf_proto_create(nla_data(tca[TCA_KIND]),
protocol, prio, chain, rtnl_held,
extack);
if (IS_ERR(tp_new)) {
err = PTR_ERR(tp_new);
goto errout_tp;
}
tp = tcf_proto_create(nla_data(tca[TCA_KIND]),
protocol, prio, chain, extack);
tp_created = 1;
tp = tcf_chain_tp_insert_unique(chain, tp_new, protocol, prio,
rtnl_held);
if (IS_ERR(tp)) {
err = PTR_ERR(tp);
goto errout;
goto errout_tp;
}
tp_created = 1;
} else if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], tp->ops->kind)) {
} else {
mutex_unlock(&chain->filter_chain_lock);
}
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;
......@@ -1631,6 +2132,7 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
goto errout;
}
} else if (n->nlmsg_flags & NLM_F_EXCL) {
tfilter_put(tp, fh);
NL_SET_ERR_MSG(extack, "Filter already exists");
err = -EEXIST;
goto errout;
......@@ -1644,25 +2146,41 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
err = tp->ops->change(net, skb, tp, cl, t->tcm_handle, tca, &fh,
n->nlmsg_flags & NLM_F_CREATE ? TCA_ACT_NOREPLACE : TCA_ACT_REPLACE,
extack);
rtnl_held, extack);
if (err == 0) {
if (tp_created)
tcf_chain_tp_insert(chain, &chain_info, tp);
tfilter_notify(net, skb, n, tp, block, q, parent, fh,
RTM_NEWTFILTER, false);
} else {
if (tp_created)
tcf_proto_destroy(tp, NULL);
RTM_NEWTFILTER, false, rtnl_held);
tfilter_put(tp, fh);
}
errout:
if (chain)
tcf_chain_put(chain);
tcf_block_release(q, block);
if (err == -EAGAIN)
if (err && tp_created)
tcf_chain_tp_delete_empty(chain, tp, rtnl_held, NULL);
errout_tp:
if (chain) {
if (tp && !IS_ERR(tp))
tcf_proto_put(tp, rtnl_held, NULL);
if (!tp_created)
tcf_chain_put(chain);
}
tcf_block_release(q, block, rtnl_held);
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;
/* Replay the request. */
goto replay;
}
return err;
errout_locked:
mutex_unlock(&chain->filter_chain_lock);
goto errout;
}
static int tc_del_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
......@@ -1678,11 +2196,12 @@ static int tc_del_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
struct Qdisc *q = NULL;
struct tcf_chain_info chain_info;
struct tcf_chain *chain = NULL;
struct tcf_block *block;
struct tcf_block *block = NULL;
struct tcf_proto *tp = NULL;
unsigned long cl = 0;
void *fh = NULL;
int err;
bool rtnl_held = false;
if (!netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN))
return -EPERM;
......@@ -1703,8 +2222,27 @@ static int tc_del_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
/* Find head of filter chain. */
block = tcf_block_find(net, &q, &parent, &cl,
t->tcm_ifindex, t->tcm_block_index, extack);
err = __tcf_qdisc_find(net, &q, &parent, t->tcm_ifindex, false, extack);
if (err)
return err;
/* 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)) ||
!tca[TCA_KIND] || !tcf_proto_is_unlocked(nla_data(tca[TCA_KIND]))) {
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);
if (IS_ERR(block)) {
err = PTR_ERR(block);
goto errout;
......@@ -1732,56 +2270,69 @@ static int tc_del_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
if (prio == 0) {
tfilter_notify_chain(net, skb, block, q, parent, n,
chain, RTM_DELTFILTER);
tcf_chain_flush(chain);
chain, RTM_DELTFILTER, rtnl_held);
tcf_chain_flush(chain, rtnl_held);
err = 0;
goto errout;
}
mutex_lock(&chain->filter_chain_lock);
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");
err = tp ? PTR_ERR(tp) : -ENOENT;
goto errout;
goto errout_locked;
} 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_locked;
} else if (t->tcm_handle == 0) {
tcf_chain_tp_remove(chain, &chain_info, tp);
mutex_unlock(&chain->filter_chain_lock);
tcf_proto_put(tp, rtnl_held, NULL);
tfilter_notify(net, skb, n, tp, block, q, parent, fh,
RTM_DELTFILTER, false, rtnl_held);
err = 0;
goto errout;
}
mutex_unlock(&chain->filter_chain_lock);
fh = tp->ops->get(tp, t->tcm_handle);
if (!fh) {
if (t->tcm_handle == 0) {
tcf_chain_tp_remove(chain, &chain_info, tp);
tfilter_notify(net, skb, n, tp, block, q, parent, fh,
RTM_DELTFILTER, false);
tcf_proto_destroy(tp, extack);
err = 0;
} else {
NL_SET_ERR_MSG(extack, "Specified filter handle not found");
err = -ENOENT;
}
NL_SET_ERR_MSG(extack, "Specified filter handle not found");
err = -ENOENT;
} else {
bool last;
err = tfilter_del_notify(net, skb, n, tp, block,
q, parent, fh, false, &last,
extack);
rtnl_held, extack);
if (err)
goto errout;
if (last) {
tcf_chain_tp_remove(chain, &chain_info, tp);
tcf_proto_destroy(tp, extack);
}
if (last)
tcf_chain_tp_delete_empty(chain, tp, rtnl_held, extack);
}
errout:
if (chain)
if (chain) {
if (tp && !IS_ERR(tp))
tcf_proto_put(tp, rtnl_held, NULL);
tcf_chain_put(chain);
tcf_block_release(q, block);
}
tcf_block_release(q, block, rtnl_held);
if (rtnl_held)
rtnl_unlock();
return err;
errout_locked:
mutex_unlock(&chain->filter_chain_lock);
goto errout;
}
static int tc_get_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
......@@ -1797,11 +2348,12 @@ static int tc_get_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
struct Qdisc *q = NULL;
struct tcf_chain_info chain_info;
struct tcf_chain *chain = NULL;
struct tcf_block *block;
struct tcf_block *block = NULL;
struct tcf_proto *tp = NULL;
unsigned long cl = 0;
void *fh = NULL;
int err;
bool rtnl_held = false;
err = nlmsg_parse(n, sizeof(*t), tca, TCA_MAX, rtm_tca_policy, extack);
if (err < 0)
......@@ -1819,8 +2371,26 @@ static int tc_get_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
/* Find head of filter chain. */
block = tcf_block_find(net, &q, &parent, &cl,
t->tcm_ifindex, t->tcm_block_index, extack);
err = __tcf_qdisc_find(net, &q, &parent, t->tcm_ifindex, false, extack);
if (err)
return err;
/* 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)) ||
!tca[TCA_KIND] || !tcf_proto_is_unlocked(nla_data(tca[TCA_KIND]))) {
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);
if (IS_ERR(block)) {
err = PTR_ERR(block);
goto errout;
......@@ -1839,8 +2409,10 @@ static int tc_get_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
goto errout;
}
mutex_lock(&chain->filter_chain_lock);
tp = tcf_chain_tp_find(chain, &chain_info, protocol,
prio, false);
mutex_unlock(&chain->filter_chain_lock);
if (!tp || IS_ERR(tp)) {
NL_SET_ERR_MSG(extack, "Filter with specified priority/protocol not found");
err = tp ? PTR_ERR(tp) : -ENOENT;
......@@ -1858,15 +2430,23 @@ static int tc_get_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
err = -ENOENT;
} else {
err = tfilter_notify(net, skb, n, tp, block, q, parent,
fh, RTM_NEWTFILTER, true);
fh, RTM_NEWTFILTER, true, rtnl_held);
if (err < 0)
NL_SET_ERR_MSG(extack, "Failed to send filter notify message");
}
tfilter_put(tp, fh);
errout:
if (chain)
if (chain) {
if (tp && !IS_ERR(tp))
tcf_proto_put(tp, rtnl_held, NULL);
tcf_chain_put(chain);
tcf_block_release(q, block);
}
tcf_block_release(q, block, rtnl_held);
if (rtnl_held)
rtnl_unlock();
return err;
}
......@@ -1887,7 +2467,7 @@ static int tcf_node_dump(struct tcf_proto *tp, void *n, struct tcf_walker *arg)
return tcf_fill_node(net, a->skb, tp, a->block, a->q, a->parent,
n, NETLINK_CB(a->cb->skb).portid,
a->cb->nlh->nlmsg_seq, NLM_F_MULTI,
RTM_NEWTFILTER);
RTM_NEWTFILTER, true);
}
static bool tcf_chain_dump(struct tcf_chain *chain, struct Qdisc *q, u32 parent,
......@@ -1897,11 +2477,15 @@ static bool tcf_chain_dump(struct tcf_chain *chain, struct Qdisc *q, u32 parent,
struct net *net = sock_net(skb->sk);
struct tcf_block *block = chain->block;
struct tcmsg *tcm = nlmsg_data(cb->nlh);
struct tcf_proto *tp, *tp_prev;
struct tcf_dump_args arg;
struct tcf_proto *tp;
for (tp = rtnl_dereference(chain->filter_chain);
tp; tp = rtnl_dereference(tp->next), (*p_index)++) {
for (tp = __tcf_get_next_proto(chain, NULL);
tp;
tp_prev = tp,
tp = __tcf_get_next_proto(chain, tp),
tcf_proto_put(tp_prev, true, NULL),
(*p_index)++) {
if (*p_index < index_start)
continue;
if (TC_H_MAJ(tcm->tcm_info) &&
......@@ -1917,9 +2501,8 @@ static bool tcf_chain_dump(struct tcf_chain *chain, struct Qdisc *q, u32 parent,
if (tcf_fill_node(net, skb, tp, block, q, parent, NULL,
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, NLM_F_MULTI,
RTM_NEWTFILTER) <= 0)
return false;
RTM_NEWTFILTER, true) <= 0)
goto errout;
cb->args[1] = 1;
}
if (!tp->ops->walk)
......@@ -1934,23 +2517,27 @@ static bool tcf_chain_dump(struct tcf_chain *chain, struct Qdisc *q, u32 parent,
arg.w.skip = cb->args[1] - 1;
arg.w.count = 0;
arg.w.cookie = cb->args[2];
tp->ops->walk(tp, &arg.w);
tp->ops->walk(tp, &arg.w, true);
cb->args[2] = arg.w.cookie;
cb->args[1] = arg.w.count + 1;
if (arg.w.stop)
return false;
goto errout;
}
return true;
errout:
tcf_proto_put(tp, true, NULL);
return false;
}
/* called with RTNL */
static int tc_dump_tfilter(struct sk_buff *skb, struct netlink_callback *cb)
{
struct tcf_chain *chain, *chain_prev;
struct net *net = sock_net(skb->sk);
struct nlattr *tca[TCA_MAX + 1];
struct Qdisc *q = NULL;
struct tcf_block *block;
struct tcf_chain *chain;
struct tcmsg *tcm = nlmsg_data(cb->nlh);
long index_start;
long index;
......@@ -2014,19 +2601,24 @@ static int tc_dump_tfilter(struct sk_buff *skb, struct netlink_callback *cb)
index_start = cb->args[0];
index = 0;
list_for_each_entry(chain, &block->chain_list, list) {
for (chain = __tcf_get_next_chain(block, NULL);
chain;
chain_prev = chain,
chain = __tcf_get_next_chain(block, chain),
tcf_chain_put(chain_prev)) {
if (tca[TCA_CHAIN] &&
nla_get_u32(tca[TCA_CHAIN]) != chain->index)
continue;
if (!tcf_chain_dump(chain, q, parent, skb, cb,
index_start, &index)) {
tcf_chain_put(chain);
err = -EMSGSIZE;
break;
}
}
if (tcm->tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK)
tcf_block_refcnt_put(block);
tcf_block_refcnt_put(block, true);
cb->args[0] = index;
out:
......@@ -2036,8 +2628,10 @@ static int tc_dump_tfilter(struct sk_buff *skb, struct netlink_callback *cb)
return skb->len;
}
static int tc_chain_fill_node(struct tcf_chain *chain, struct net *net,
struct sk_buff *skb, struct tcf_block *block,
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,
u32 portid, u32 seq, u16 flags, int event)
{
unsigned char *b = skb_tail_pointer(skb);
......@@ -2046,8 +2640,8 @@ static int tc_chain_fill_node(struct tcf_chain *chain, struct net *net,
struct tcmsg *tcm;
void *priv;
ops = chain->tmplt_ops;
priv = chain->tmplt_priv;
ops = tmplt_ops;
priv = tmplt_priv;
nlh = nlmsg_put(skb, portid, seq, event, sizeof(*tcm), flags);
if (!nlh)
......@@ -2065,7 +2659,7 @@ static int tc_chain_fill_node(struct tcf_chain *chain, struct net *net,
tcm->tcm_block_index = block->index;
}
if (nla_put_u32(skb, TCA_CHAIN, chain->index))
if (nla_put_u32(skb, TCA_CHAIN, chain_index))
goto nla_put_failure;
if (ops) {
......@@ -2096,7 +2690,8 @@ static int tc_chain_notify(struct tcf_chain *chain, struct sk_buff *oskb,
if (!skb)
return -ENOBUFS;
if (tc_chain_fill_node(chain, net, skb, block, portid,
if (tc_chain_fill_node(chain->tmplt_ops, chain->tmplt_priv,
chain->index, net, skb, block, portid,
seq, flags, event) <= 0) {
kfree_skb(skb);
return -EINVAL;
......@@ -2108,6 +2703,31 @@ static int tc_chain_notify(struct tcf_chain *chain, struct sk_buff *oskb,
return rtnetlink_send(skb, net, portid, RTNLGRP_TC, flags & NLM_F_ECHO);
}
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);
}
static int tc_chain_tmplt_add(struct tcf_chain *chain, struct net *net,
struct nlattr **tca,
struct netlink_ext_ack *extack)
......@@ -2119,7 +2739,7 @@ static int tc_chain_tmplt_add(struct tcf_chain *chain, struct net *net,
if (!tca[TCA_KIND])
return 0;
ops = tcf_proto_lookup_ops(nla_data(tca[TCA_KIND]), extack);
ops = tcf_proto_lookup_ops(nla_data(tca[TCA_KIND]), true, extack);
if (IS_ERR(ops))
return PTR_ERR(ops);
if (!ops->tmplt_create || !ops->tmplt_destroy || !ops->tmplt_dump) {
......@@ -2137,16 +2757,15 @@ static int tc_chain_tmplt_add(struct tcf_chain *chain, struct net *net,
return 0;
}
static void tc_chain_tmplt_del(struct tcf_chain *chain)
static void tc_chain_tmplt_del(const struct tcf_proto_ops *tmplt_ops,
void *tmplt_priv)
{
const struct tcf_proto_ops *ops = chain->tmplt_ops;
/* If template ops are set, no work to do for us. */
if (!ops)
if (!tmplt_ops)
return;
ops->tmplt_destroy(chain->tmplt_priv);
module_put(ops->owner);
tmplt_ops->tmplt_destroy(tmplt_priv);
module_put(tmplt_ops->owner);
}
/* Add/delete/get a chain */
......@@ -2189,6 +2808,8 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n,
err = -EINVAL;
goto errout_block;
}
mutex_lock(&block->lock);
chain = tcf_chain_lookup(block, chain_index);
if (n->nlmsg_type == RTM_NEWCHAIN) {
if (chain) {
......@@ -2200,54 +2821,61 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n,
} else {
NL_SET_ERR_MSG(extack, "Filter chain already exists");
err = -EEXIST;
goto errout_block;
goto errout_block_locked;
}
} 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");
err = -ENOENT;
goto errout_block;
goto errout_block_locked;
}
chain = tcf_chain_create(block, chain_index);
if (!chain) {
NL_SET_ERR_MSG(extack, "Failed to create filter chain");
err = -ENOMEM;
goto errout_block;
goto errout_block_locked;
}
}
} else {
if (!chain || tcf_chain_held_by_acts_only(chain)) {
NL_SET_ERR_MSG(extack, "Cannot find specified filter chain");
err = -EINVAL;
goto errout_block;
goto errout_block_locked;
}
tcf_chain_hold(chain);
}
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);
switch (n->nlmsg_type) {
case RTM_NEWCHAIN:
err = tc_chain_tmplt_add(chain, net, tca, extack);
if (err)
if (err) {
tcf_chain_put_explicitly_created(chain);
goto errout;
/* 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;
}
tc_chain_notify(chain, NULL, 0, NLM_F_CREATE | NLM_F_EXCL,
RTM_NEWCHAIN, false);
break;
case RTM_DELCHAIN:
tfilter_notify_chain(net, skb, block, q, parent, n,
chain, RTM_DELTFILTER);
chain, RTM_DELTFILTER, true);
/* Flush the chain first as the user requested chain removal. */
tcf_chain_flush(chain);
tcf_chain_flush(chain, true);
/* In case the chain was successfully deleted, put a reference
* to the chain previously taken during addition.
*/
tcf_chain_put_explicitly_created(chain);
chain->explicitly_created = false;
break;
case RTM_GETCHAIN:
err = tc_chain_notify(chain, skb, n->nlmsg_seq,
......@@ -2264,21 +2892,25 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n,
errout:
tcf_chain_put(chain);
errout_block:
tcf_block_release(q, block);
tcf_block_release(q, block, true);
if (err == -EAGAIN)
/* Replay the request. */
goto replay;
return err;
errout_block_locked:
mutex_unlock(&block->lock);
goto errout_block;
}
/* called with RTNL */
static int tc_dump_chain(struct sk_buff *skb, struct netlink_callback *cb)
{
struct tcf_chain *chain, *chain_prev;
struct net *net = sock_net(skb->sk);
struct nlattr *tca[TCA_MAX + 1];
struct Qdisc *q = NULL;
struct tcf_block *block;
struct tcf_chain *chain;
struct tcmsg *tcm = nlmsg_data(cb->nlh);
long index_start;
long index;
......@@ -2342,7 +2974,11 @@ static int tc_dump_chain(struct sk_buff *skb, struct netlink_callback *cb)
index_start = cb->args[0];
index = 0;
list_for_each_entry(chain, &block->chain_list, list) {
for (chain = __tcf_get_next_chain(block, NULL);
chain;
chain_prev = chain,
chain = __tcf_get_next_chain(block, chain),
tcf_chain_put(chain_prev)) {
if ((tca[TCA_CHAIN] &&
nla_get_u32(tca[TCA_CHAIN]) != chain->index))
continue;
......@@ -2350,19 +2986,20 @@ static int tc_dump_chain(struct sk_buff *skb, struct netlink_callback *cb)
index++;
continue;
}
if (tcf_chain_held_by_acts_only(chain))
continue;
err = tc_chain_fill_node(chain, net, skb, block,
err = tc_chain_fill_node(chain->tmplt_ops, chain->tmplt_priv,
chain->index, net, skb, block,
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, NLM_F_MULTI,
RTM_NEWCHAIN);
if (err <= 0)
if (err <= 0) {
tcf_chain_put(chain);
break;
}
index++;
}
if (tcm->tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK)
tcf_block_refcnt_put(block);
tcf_block_refcnt_put(block, true);
cb->args[0] = index;
out:
......@@ -2384,7 +3021,7 @@ EXPORT_SYMBOL(tcf_exts_destroy);
int tcf_exts_validate(struct net *net, struct tcf_proto *tp, struct nlattr **tb,
struct nlattr *rate_tlv, struct tcf_exts *exts, bool ovr,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
#ifdef CONFIG_NET_CLS_ACT
{
......@@ -2394,7 +3031,8 @@ int tcf_exts_validate(struct net *net, struct tcf_proto *tp, struct nlattr **tb,
if (exts->police && tb[exts->police]) {
act = tcf_action_init_1(net, tp, tb[exts->police],
rate_tlv, "police", ovr,
TCA_ACT_BIND, true, extack);
TCA_ACT_BIND, rtnl_held,
extack);
if (IS_ERR(act))
return PTR_ERR(act);
......@@ -2406,8 +3044,8 @@ int tcf_exts_validate(struct net *net, struct tcf_proto *tp, struct nlattr **tb,
err = tcf_action_init(net, tp, tb[exts->action],
rate_tlv, NULL, ovr, TCA_ACT_BIND,
exts->actions, &attr_size, true,
extack);
exts->actions, &attr_size,
rtnl_held, extack);
if (err < 0)
return err;
exts->nr_actions = err;
......@@ -2671,10 +3309,12 @@ static int __init tc_filter_init(void)
if (err)
goto err_rhash_setup_block_ht;
rtnl_register(PF_UNSPEC, RTM_NEWTFILTER, tc_new_tfilter, NULL, 0);
rtnl_register(PF_UNSPEC, RTM_DELTFILTER, tc_del_tfilter, NULL, 0);
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);
rtnl_register(PF_UNSPEC, RTM_GETTFILTER, tc_get_tfilter,
tc_dump_tfilter, 0);
tc_dump_tfilter, RTNL_FLAG_DOIT_UNLOCKED);
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,
......
......@@ -107,7 +107,8 @@ static void basic_delete_filter_work(struct work_struct *work)
rtnl_unlock();
}
static void basic_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
static void basic_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct basic_head *head = rtnl_dereference(tp->root);
struct basic_filter *f, *n;
......@@ -126,7 +127,7 @@ static void basic_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
}
static int basic_delete(struct tcf_proto *tp, void *arg, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct basic_head *head = rtnl_dereference(tp->root);
struct basic_filter *f = arg;
......@@ -153,7 +154,7 @@ static int basic_set_parms(struct net *net, struct tcf_proto *tp,
{
int err;
err = tcf_exts_validate(net, tp, tb, est, &f->exts, ovr, extack);
err = tcf_exts_validate(net, tp, tb, est, &f->exts, ovr, true, extack);
if (err < 0)
return err;
......@@ -173,7 +174,7 @@ static int basic_set_parms(struct net *net, struct tcf_proto *tp,
static int basic_change(struct net *net, struct sk_buff *in_skb,
struct tcf_proto *tp, unsigned long base, u32 handle,
struct nlattr **tca, void **arg, bool ovr,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
int err;
struct basic_head *head = rtnl_dereference(tp->root);
......@@ -247,7 +248,8 @@ static int basic_change(struct net *net, struct sk_buff *in_skb,
return err;
}
static void basic_walk(struct tcf_proto *tp, struct tcf_walker *arg)
static void basic_walk(struct tcf_proto *tp, struct tcf_walker *arg,
bool rtnl_held)
{
struct basic_head *head = rtnl_dereference(tp->root);
struct basic_filter *f;
......@@ -274,7 +276,7 @@ static void basic_bind_class(void *fh, u32 classid, unsigned long cl)
}
static int basic_dump(struct net *net, struct tcf_proto *tp, void *fh,
struct sk_buff *skb, struct tcmsg *t)
struct sk_buff *skb, struct tcmsg *t, bool rtnl_held)
{
struct tc_basic_pcnt gpf = {};
struct basic_filter *f = fh;
......
......@@ -298,7 +298,7 @@ static void __cls_bpf_delete(struct tcf_proto *tp, struct cls_bpf_prog *prog,
}
static int cls_bpf_delete(struct tcf_proto *tp, void *arg, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct cls_bpf_head *head = rtnl_dereference(tp->root);
......@@ -307,7 +307,7 @@ static int cls_bpf_delete(struct tcf_proto *tp, void *arg, bool *last,
return 0;
}
static void cls_bpf_destroy(struct tcf_proto *tp,
static void cls_bpf_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct cls_bpf_head *head = rtnl_dereference(tp->root);
......@@ -417,7 +417,8 @@ static int cls_bpf_set_parms(struct net *net, struct tcf_proto *tp,
if ((!is_bpf && !is_ebpf) || (is_bpf && is_ebpf))
return -EINVAL;
ret = tcf_exts_validate(net, tp, tb, est, &prog->exts, ovr, extack);
ret = tcf_exts_validate(net, tp, tb, est, &prog->exts, ovr, true,
extack);
if (ret < 0)
return ret;
......@@ -455,7 +456,8 @@ static int cls_bpf_set_parms(struct net *net, struct tcf_proto *tp,
static int cls_bpf_change(struct net *net, struct sk_buff *in_skb,
struct tcf_proto *tp, unsigned long base,
u32 handle, struct nlattr **tca,
void **arg, bool ovr, struct netlink_ext_ack *extack)
void **arg, bool ovr, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct cls_bpf_head *head = rtnl_dereference(tp->root);
struct cls_bpf_prog *oldprog = *arg;
......@@ -575,7 +577,7 @@ static int cls_bpf_dump_ebpf_info(const struct cls_bpf_prog *prog,
}
static int cls_bpf_dump(struct net *net, struct tcf_proto *tp, void *fh,
struct sk_buff *skb, struct tcmsg *tm)
struct sk_buff *skb, struct tcmsg *tm, bool rtnl_held)
{
struct cls_bpf_prog *prog = fh;
struct nlattr *nest;
......@@ -635,7 +637,8 @@ static void cls_bpf_bind_class(void *fh, u32 classid, unsigned long cl)
prog->res.class = cl;
}
static void cls_bpf_walk(struct tcf_proto *tp, struct tcf_walker *arg)
static void cls_bpf_walk(struct tcf_proto *tp, struct tcf_walker *arg,
bool rtnl_held)
{
struct cls_bpf_head *head = rtnl_dereference(tp->root);
struct cls_bpf_prog *prog;
......
......@@ -78,7 +78,7 @@ static void cls_cgroup_destroy_work(struct work_struct *work)
static int cls_cgroup_change(struct net *net, struct sk_buff *in_skb,
struct tcf_proto *tp, unsigned long base,
u32 handle, struct nlattr **tca,
void **arg, bool ovr,
void **arg, bool ovr, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct nlattr *tb[TCA_CGROUP_MAX + 1];
......@@ -110,7 +110,7 @@ static int cls_cgroup_change(struct net *net, struct sk_buff *in_skb,
goto errout;
err = tcf_exts_validate(net, tp, tb, tca[TCA_RATE], &new->exts, ovr,
extack);
true, extack);
if (err < 0)
goto errout;
......@@ -130,7 +130,7 @@ static int cls_cgroup_change(struct net *net, struct sk_buff *in_skb,
return err;
}
static void cls_cgroup_destroy(struct tcf_proto *tp,
static void cls_cgroup_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct cls_cgroup_head *head = rtnl_dereference(tp->root);
......@@ -145,12 +145,13 @@ static void cls_cgroup_destroy(struct tcf_proto *tp,
}
static int cls_cgroup_delete(struct tcf_proto *tp, void *arg, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
return -EOPNOTSUPP;
}
static void cls_cgroup_walk(struct tcf_proto *tp, struct tcf_walker *arg)
static void cls_cgroup_walk(struct tcf_proto *tp, struct tcf_walker *arg,
bool rtnl_held)
{
struct cls_cgroup_head *head = rtnl_dereference(tp->root);
......@@ -166,7 +167,7 @@ static void cls_cgroup_walk(struct tcf_proto *tp, struct tcf_walker *arg)
}
static int cls_cgroup_dump(struct net *net, struct tcf_proto *tp, void *fh,
struct sk_buff *skb, struct tcmsg *t)
struct sk_buff *skb, struct tcmsg *t, bool rtnl_held)
{
struct cls_cgroup_head *head = rtnl_dereference(tp->root);
struct nlattr *nest;
......
......@@ -391,7 +391,8 @@ static void flow_destroy_filter_work(struct work_struct *work)
static int flow_change(struct net *net, struct sk_buff *in_skb,
struct tcf_proto *tp, unsigned long base,
u32 handle, struct nlattr **tca,
void **arg, bool ovr, struct netlink_ext_ack *extack)
void **arg, bool ovr, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct flow_head *head = rtnl_dereference(tp->root);
struct flow_filter *fold, *fnew;
......@@ -445,7 +446,7 @@ static int flow_change(struct net *net, struct sk_buff *in_skb,
goto err2;
err = tcf_exts_validate(net, tp, tb, tca[TCA_RATE], &fnew->exts, ovr,
extack);
true, extack);
if (err < 0)
goto err2;
......@@ -566,7 +567,7 @@ static int flow_change(struct net *net, struct sk_buff *in_skb,
}
static int flow_delete(struct tcf_proto *tp, void *arg, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct flow_head *head = rtnl_dereference(tp->root);
struct flow_filter *f = arg;
......@@ -590,7 +591,8 @@ static int flow_init(struct tcf_proto *tp)
return 0;
}
static void flow_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
static void flow_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct flow_head *head = rtnl_dereference(tp->root);
struct flow_filter *f, *next;
......@@ -617,7 +619,7 @@ static void *flow_get(struct tcf_proto *tp, u32 handle)
}
static int flow_dump(struct net *net, struct tcf_proto *tp, void *fh,
struct sk_buff *skb, struct tcmsg *t)
struct sk_buff *skb, struct tcmsg *t, bool rtnl_held)
{
struct flow_filter *f = fh;
struct nlattr *nest;
......@@ -677,7 +679,8 @@ static int flow_dump(struct net *net, struct tcf_proto *tp, void *fh,
return -1;
}
static void flow_walk(struct tcf_proto *tp, struct tcf_walker *arg)
static void flow_walk(struct tcf_proto *tp, struct tcf_walker *arg,
bool rtnl_held)
{
struct flow_head *head = rtnl_dereference(tp->root);
struct flow_filter *f;
......
......@@ -465,7 +465,8 @@ static void fl_destroy_sleepable(struct work_struct *work)
module_put(THIS_MODULE);
}
static void fl_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
static void fl_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct cls_fl_head *head = rtnl_dereference(tp->root);
struct fl_flow_mask *mask, *next_mask;
......@@ -1272,7 +1273,8 @@ static int fl_set_parms(struct net *net, struct tcf_proto *tp,
{
int err;
err = tcf_exts_validate(net, tp, tb, est, &f->exts, ovr, extack);
err = tcf_exts_validate(net, tp, tb, est, &f->exts, ovr, true,
extack);
if (err < 0)
return err;
......@@ -1299,7 +1301,8 @@ static int fl_set_parms(struct net *net, struct tcf_proto *tp,
static int fl_change(struct net *net, struct sk_buff *in_skb,
struct tcf_proto *tp, unsigned long base,
u32 handle, struct nlattr **tca,
void **arg, bool ovr, struct netlink_ext_ack *extack)
void **arg, bool ovr, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct cls_fl_head *head = rtnl_dereference(tp->root);
struct cls_fl_filter *fold = *arg;
......@@ -1436,7 +1439,7 @@ static int fl_change(struct net *net, struct sk_buff *in_skb,
}
static int fl_delete(struct tcf_proto *tp, void *arg, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct cls_fl_head *head = rtnl_dereference(tp->root);
struct cls_fl_filter *f = arg;
......@@ -1448,7 +1451,8 @@ static int fl_delete(struct tcf_proto *tp, void *arg, bool *last,
return 0;
}
static void fl_walk(struct tcf_proto *tp, struct tcf_walker *arg)
static void fl_walk(struct tcf_proto *tp, struct tcf_walker *arg,
bool rtnl_held)
{
struct cls_fl_head *head = rtnl_dereference(tp->root);
struct cls_fl_filter *f;
......@@ -2043,7 +2047,7 @@ static int fl_dump_key(struct sk_buff *skb, struct net *net,
}
static int fl_dump(struct net *net, struct tcf_proto *tp, void *fh,
struct sk_buff *skb, struct tcmsg *t)
struct sk_buff *skb, struct tcmsg *t, bool rtnl_held)
{
struct cls_fl_filter *f = fh;
struct nlattr *nest;
......
......@@ -139,7 +139,8 @@ static void fw_delete_filter_work(struct work_struct *work)
rtnl_unlock();
}
static void fw_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
static void fw_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct fw_head *head = rtnl_dereference(tp->root);
struct fw_filter *f;
......@@ -163,7 +164,7 @@ static void fw_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
}
static int fw_delete(struct tcf_proto *tp, void *arg, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct fw_head *head = rtnl_dereference(tp->root);
struct fw_filter *f = arg;
......@@ -217,7 +218,7 @@ static int fw_set_parms(struct net *net, struct tcf_proto *tp,
int err;
err = tcf_exts_validate(net, tp, tb, tca[TCA_RATE], &f->exts, ovr,
extack);
true, extack);
if (err < 0)
return err;
......@@ -250,7 +251,8 @@ static int fw_set_parms(struct net *net, struct tcf_proto *tp,
static int fw_change(struct net *net, struct sk_buff *in_skb,
struct tcf_proto *tp, unsigned long base,
u32 handle, struct nlattr **tca, void **arg,
bool ovr, struct netlink_ext_ack *extack)
bool ovr, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct fw_head *head = rtnl_dereference(tp->root);
struct fw_filter *f = *arg;
......@@ -354,7 +356,8 @@ static int fw_change(struct net *net, struct sk_buff *in_skb,
return err;
}
static void fw_walk(struct tcf_proto *tp, struct tcf_walker *arg)
static void fw_walk(struct tcf_proto *tp, struct tcf_walker *arg,
bool rtnl_held)
{
struct fw_head *head = rtnl_dereference(tp->root);
int h;
......@@ -384,7 +387,7 @@ static void fw_walk(struct tcf_proto *tp, struct tcf_walker *arg)
}
static int fw_dump(struct net *net, struct tcf_proto *tp, void *fh,
struct sk_buff *skb, struct tcmsg *t)
struct sk_buff *skb, struct tcmsg *t, bool rtnl_held)
{
struct fw_head *head = rtnl_dereference(tp->root);
struct fw_filter *f = fh;
......
......@@ -109,7 +109,8 @@ static int mall_replace_hw_filter(struct tcf_proto *tp,
return 0;
}
static void mall_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
static void mall_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct cls_mall_head *head = rtnl_dereference(tp->root);
......@@ -145,7 +146,8 @@ static int mall_set_parms(struct net *net, struct tcf_proto *tp,
{
int err;
err = tcf_exts_validate(net, tp, tb, est, &head->exts, ovr, extack);
err = tcf_exts_validate(net, tp, tb, est, &head->exts, ovr, true,
extack);
if (err < 0)
return err;
......@@ -159,7 +161,8 @@ static int mall_set_parms(struct net *net, struct tcf_proto *tp,
static int mall_change(struct net *net, struct sk_buff *in_skb,
struct tcf_proto *tp, unsigned long base,
u32 handle, struct nlattr **tca,
void **arg, bool ovr, struct netlink_ext_ack *extack)
void **arg, bool ovr, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct cls_mall_head *head = rtnl_dereference(tp->root);
struct nlattr *tb[TCA_MATCHALL_MAX + 1];
......@@ -232,12 +235,13 @@ static int mall_change(struct net *net, struct sk_buff *in_skb,
}
static int mall_delete(struct tcf_proto *tp, void *arg, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
return -EOPNOTSUPP;
}
static void mall_walk(struct tcf_proto *tp, struct tcf_walker *arg)
static void mall_walk(struct tcf_proto *tp, struct tcf_walker *arg,
bool rtnl_held)
{
struct cls_mall_head *head = rtnl_dereference(tp->root);
......@@ -279,7 +283,7 @@ static int mall_reoffload(struct tcf_proto *tp, bool add, tc_setup_cb_t *cb,
}
static int mall_dump(struct net *net, struct tcf_proto *tp, void *fh,
struct sk_buff *skb, struct tcmsg *t)
struct sk_buff *skb, struct tcmsg *t, bool rtnl_held)
{
struct tc_matchall_pcnt gpf = {};
struct cls_mall_head *head = fh;
......
......@@ -276,7 +276,8 @@ static void route4_queue_work(struct route4_filter *f)
tcf_queue_work(&f->rwork, route4_delete_filter_work);
}
static void route4_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
static void route4_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct route4_head *head = rtnl_dereference(tp->root);
int h1, h2;
......@@ -312,7 +313,7 @@ static void route4_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
}
static int route4_delete(struct tcf_proto *tp, void *arg, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct route4_head *head = rtnl_dereference(tp->root);
struct route4_filter *f = arg;
......@@ -393,7 +394,7 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp,
struct route4_bucket *b;
int err;
err = tcf_exts_validate(net, tp, tb, est, &f->exts, ovr, extack);
err = tcf_exts_validate(net, tp, tb, est, &f->exts, ovr, true, extack);
if (err < 0)
return err;
......@@ -468,7 +469,7 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp,
static int route4_change(struct net *net, struct sk_buff *in_skb,
struct tcf_proto *tp, unsigned long base, u32 handle,
struct nlattr **tca, void **arg, bool ovr,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct route4_head *head = rtnl_dereference(tp->root);
struct route4_filter __rcu **fp;
......@@ -560,7 +561,8 @@ static int route4_change(struct net *net, struct sk_buff *in_skb,
return err;
}
static void route4_walk(struct tcf_proto *tp, struct tcf_walker *arg)
static void route4_walk(struct tcf_proto *tp, struct tcf_walker *arg,
bool rtnl_held)
{
struct route4_head *head = rtnl_dereference(tp->root);
unsigned int h, h1;
......@@ -597,7 +599,7 @@ static void route4_walk(struct tcf_proto *tp, struct tcf_walker *arg)
}
static int route4_dump(struct net *net, struct tcf_proto *tp, void *fh,
struct sk_buff *skb, struct tcmsg *t)
struct sk_buff *skb, struct tcmsg *t, bool rtnl_held)
{
struct route4_filter *f = fh;
struct nlattr *nest;
......
......@@ -312,7 +312,8 @@ static void rsvp_delete_filter(struct tcf_proto *tp, struct rsvp_filter *f)
__rsvp_delete_filter(f);
}
static void rsvp_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
static void rsvp_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct rsvp_head *data = rtnl_dereference(tp->root);
int h1, h2;
......@@ -341,7 +342,7 @@ static void rsvp_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
}
static int rsvp_delete(struct tcf_proto *tp, void *arg, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct rsvp_head *head = rtnl_dereference(tp->root);
struct rsvp_filter *nfp, *f = arg;
......@@ -477,7 +478,8 @@ static int rsvp_change(struct net *net, struct sk_buff *in_skb,
struct tcf_proto *tp, unsigned long base,
u32 handle,
struct nlattr **tca,
void **arg, bool ovr, struct netlink_ext_ack *extack)
void **arg, bool ovr, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct rsvp_head *data = rtnl_dereference(tp->root);
struct rsvp_filter *f, *nfp;
......@@ -502,7 +504,8 @@ static int rsvp_change(struct net *net, struct sk_buff *in_skb,
err = tcf_exts_init(&e, TCA_RSVP_ACT, TCA_RSVP_POLICE);
if (err < 0)
return err;
err = tcf_exts_validate(net, tp, tb, tca[TCA_RATE], &e, ovr, extack);
err = tcf_exts_validate(net, tp, tb, tca[TCA_RATE], &e, ovr, true,
extack);
if (err < 0)
goto errout2;
......@@ -654,7 +657,8 @@ static int rsvp_change(struct net *net, struct sk_buff *in_skb,
return err;
}
static void rsvp_walk(struct tcf_proto *tp, struct tcf_walker *arg)
static void rsvp_walk(struct tcf_proto *tp, struct tcf_walker *arg,
bool rtnl_held)
{
struct rsvp_head *head = rtnl_dereference(tp->root);
unsigned int h, h1;
......@@ -688,7 +692,7 @@ static void rsvp_walk(struct tcf_proto *tp, struct tcf_walker *arg)
}
static int rsvp_dump(struct net *net, struct tcf_proto *tp, void *fh,
struct sk_buff *skb, struct tcmsg *t)
struct sk_buff *skb, struct tcmsg *t, bool rtnl_held)
{
struct rsvp_filter *f = fh;
struct rsvp_session *s;
......
......@@ -173,7 +173,7 @@ static void tcindex_destroy_fexts_work(struct work_struct *work)
}
static int tcindex_delete(struct tcf_proto *tp, void *arg, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct tcindex_data *p = rtnl_dereference(tp->root);
struct tcindex_filter_result *r = arg;
......@@ -226,7 +226,7 @@ static int tcindex_destroy_element(struct tcf_proto *tp,
{
bool last;
return tcindex_delete(tp, arg, &last, NULL);
return tcindex_delete(tp, arg, &last, false, NULL);
}
static void __tcindex_destroy(struct rcu_head *head)
......@@ -314,7 +314,7 @@ tcindex_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base,
err = tcf_exts_init(&e, TCA_TCINDEX_ACT, TCA_TCINDEX_POLICE);
if (err < 0)
return err;
err = tcf_exts_validate(net, tp, tb, est, &e, ovr, extack);
err = tcf_exts_validate(net, tp, tb, est, &e, ovr, true, extack);
if (err < 0)
goto errout;
......@@ -499,7 +499,7 @@ static int
tcindex_change(struct net *net, struct sk_buff *in_skb,
struct tcf_proto *tp, unsigned long base, u32 handle,
struct nlattr **tca, void **arg, bool ovr,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct nlattr *opt = tca[TCA_OPTIONS];
struct nlattr *tb[TCA_TCINDEX_MAX + 1];
......@@ -522,7 +522,8 @@ tcindex_change(struct net *net, struct sk_buff *in_skb,
tca[TCA_RATE], ovr, extack);
}
static void tcindex_walk(struct tcf_proto *tp, struct tcf_walker *walker)
static void tcindex_walk(struct tcf_proto *tp, struct tcf_walker *walker,
bool rtnl_held)
{
struct tcindex_data *p = rtnl_dereference(tp->root);
struct tcindex_filter *f, *next;
......@@ -558,7 +559,7 @@ static void tcindex_walk(struct tcf_proto *tp, struct tcf_walker *walker)
}
}
static void tcindex_destroy(struct tcf_proto *tp,
static void tcindex_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct tcindex_data *p = rtnl_dereference(tp->root);
......@@ -568,14 +569,14 @@ static void tcindex_destroy(struct tcf_proto *tp,
walker.count = 0;
walker.skip = 0;
walker.fn = tcindex_destroy_element;
tcindex_walk(tp, &walker);
tcindex_walk(tp, &walker, true);
call_rcu(&p->rcu, __tcindex_destroy);
}
static int tcindex_dump(struct net *net, struct tcf_proto *tp, void *fh,
struct sk_buff *skb, struct tcmsg *t)
struct sk_buff *skb, struct tcmsg *t, bool rtnl_held)
{
struct tcindex_data *p = rtnl_dereference(tp->root);
struct tcindex_filter_result *r = fh;
......
......@@ -629,7 +629,8 @@ static int u32_destroy_hnode(struct tcf_proto *tp, struct tc_u_hnode *ht,
return -ENOENT;
}
static void u32_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
static void u32_destroy(struct tcf_proto *tp, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct tc_u_common *tp_c = tp->data;
struct tc_u_hnode *root_ht = rtnl_dereference(tp->root);
......@@ -663,7 +664,7 @@ static void u32_destroy(struct tcf_proto *tp, struct netlink_ext_ack *extack)
}
static int u32_delete(struct tcf_proto *tp, void *arg, bool *last,
struct netlink_ext_ack *extack)
bool rtnl_held, struct netlink_ext_ack *extack)
{
struct tc_u_hnode *ht = arg;
struct tc_u_common *tp_c = tp->data;
......@@ -726,7 +727,7 @@ static int u32_set_parms(struct net *net, struct tcf_proto *tp,
{
int err;
err = tcf_exts_validate(net, tp, tb, est, &n->exts, ovr, extack);
err = tcf_exts_validate(net, tp, tb, est, &n->exts, ovr, true, extack);
if (err < 0)
return err;
......@@ -858,7 +859,7 @@ static struct tc_u_knode *u32_init_knode(struct tcf_proto *tp,
static int u32_change(struct net *net, struct sk_buff *in_skb,
struct tcf_proto *tp, unsigned long base, u32 handle,
struct nlattr **tca, void **arg, bool ovr,
struct nlattr **tca, void **arg, bool ovr, bool rtnl_held,
struct netlink_ext_ack *extack)
{
struct tc_u_common *tp_c = tp->data;
......@@ -1123,7 +1124,8 @@ static int u32_change(struct net *net, struct sk_buff *in_skb,
return err;
}
static void u32_walk(struct tcf_proto *tp, struct tcf_walker *arg)
static void u32_walk(struct tcf_proto *tp, struct tcf_walker *arg,
bool rtnl_held)
{
struct tc_u_common *tp_c = tp->data;
struct tc_u_hnode *ht;
......@@ -1281,7 +1283,7 @@ static void u32_bind_class(void *fh, u32 classid, unsigned long cl)
}
static int u32_dump(struct net *net, struct tcf_proto *tp, void *fh,
struct sk_buff *skb, struct tcmsg *t)
struct sk_buff *skb, struct tcmsg *t, bool rtnl_held)
{
struct tc_u_knode *n = fh;
struct tc_u_hnode *ht_up, *ht_down;
......
......@@ -1909,17 +1909,19 @@ static void tc_bind_tclass(struct Qdisc *q, u32 portid, u32 clid,
block = cops->tcf_block(q, cl, NULL);
if (!block)
return;
list_for_each_entry(chain, &block->chain_list, list) {
for (chain = tcf_get_next_chain(block, NULL);
chain;
chain = tcf_get_next_chain(block, chain)) {
struct tcf_proto *tp;
for (tp = rtnl_dereference(chain->filter_chain);
tp; tp = rtnl_dereference(tp->next)) {
for (tp = tcf_get_next_proto(chain, NULL, true);
tp; tp = tcf_get_next_proto(chain, tp, true)) {
struct tcf_bind_args arg = {};
arg.w.fn = tcf_node_bind;
arg.classid = clid;
arg.cl = new_cl;
tp->ops->walk(tp, &arg.w);
tp->ops->walk(tp, &arg.w, true);
}
}
}
......
......@@ -1366,7 +1366,11 @@ static void mini_qdisc_rcu_func(struct rcu_head *head)
void mini_qdisc_pair_swap(struct mini_Qdisc_pair *miniqp,
struct tcf_proto *tp_head)
{
struct mini_Qdisc *miniq_old = rtnl_dereference(*miniqp->p_miniq);
/* Protected with chain0->filter_chain_lock.
* Can't access chain directly because tp_head can be NULL.
*/
struct mini_Qdisc *miniq_old =
rcu_dereference_protected(*miniqp->p_miniq, 1);
struct mini_Qdisc *miniq;
if (!tp_head) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment