162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * net/sched/act_mirred.c	packet mirroring and redirect actions
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:	Jamal Hadi Salim (2002-4)
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * TODO: Add ingress support (and socket redirect support)
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/types.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/string.h>
1362306a36Sopenharmony_ci#include <linux/errno.h>
1462306a36Sopenharmony_ci#include <linux/skbuff.h>
1562306a36Sopenharmony_ci#include <linux/rtnetlink.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <linux/init.h>
1862306a36Sopenharmony_ci#include <linux/gfp.h>
1962306a36Sopenharmony_ci#include <linux/if_arp.h>
2062306a36Sopenharmony_ci#include <net/net_namespace.h>
2162306a36Sopenharmony_ci#include <net/netlink.h>
2262306a36Sopenharmony_ci#include <net/dst.h>
2362306a36Sopenharmony_ci#include <net/pkt_sched.h>
2462306a36Sopenharmony_ci#include <net/pkt_cls.h>
2562306a36Sopenharmony_ci#include <linux/tc_act/tc_mirred.h>
2662306a36Sopenharmony_ci#include <net/tc_act/tc_mirred.h>
2762306a36Sopenharmony_ci#include <net/tc_wrapper.h>
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic LIST_HEAD(mirred_list);
3062306a36Sopenharmony_cistatic DEFINE_SPINLOCK(mirred_list_lock);
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define MIRRED_NEST_LIMIT    4
3362306a36Sopenharmony_cistatic DEFINE_PER_CPU(unsigned int, mirred_nest_level);
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic bool tcf_mirred_is_act_redirect(int action)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	return action == TCA_EGRESS_REDIR || action == TCA_INGRESS_REDIR;
3862306a36Sopenharmony_ci}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic bool tcf_mirred_act_wants_ingress(int action)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	switch (action) {
4362306a36Sopenharmony_ci	case TCA_EGRESS_REDIR:
4462306a36Sopenharmony_ci	case TCA_EGRESS_MIRROR:
4562306a36Sopenharmony_ci		return false;
4662306a36Sopenharmony_ci	case TCA_INGRESS_REDIR:
4762306a36Sopenharmony_ci	case TCA_INGRESS_MIRROR:
4862306a36Sopenharmony_ci		return true;
4962306a36Sopenharmony_ci	default:
5062306a36Sopenharmony_ci		BUG();
5162306a36Sopenharmony_ci	}
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic bool tcf_mirred_can_reinsert(int action)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	switch (action) {
5762306a36Sopenharmony_ci	case TC_ACT_SHOT:
5862306a36Sopenharmony_ci	case TC_ACT_STOLEN:
5962306a36Sopenharmony_ci	case TC_ACT_QUEUED:
6062306a36Sopenharmony_ci	case TC_ACT_TRAP:
6162306a36Sopenharmony_ci		return true;
6262306a36Sopenharmony_ci	}
6362306a36Sopenharmony_ci	return false;
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic struct net_device *tcf_mirred_dev_dereference(struct tcf_mirred *m)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	return rcu_dereference_protected(m->tcfm_dev,
6962306a36Sopenharmony_ci					 lockdep_is_held(&m->tcf_lock));
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic void tcf_mirred_release(struct tc_action *a)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	struct tcf_mirred *m = to_mirred(a);
7562306a36Sopenharmony_ci	struct net_device *dev;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	spin_lock(&mirred_list_lock);
7862306a36Sopenharmony_ci	list_del(&m->tcfm_list);
7962306a36Sopenharmony_ci	spin_unlock(&mirred_list_lock);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	/* last reference to action, no need to lock */
8262306a36Sopenharmony_ci	dev = rcu_dereference_protected(m->tcfm_dev, 1);
8362306a36Sopenharmony_ci	netdev_put(dev, &m->tcfm_dev_tracker);
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic const struct nla_policy mirred_policy[TCA_MIRRED_MAX + 1] = {
8762306a36Sopenharmony_ci	[TCA_MIRRED_PARMS]	= { .len = sizeof(struct tc_mirred) },
8862306a36Sopenharmony_ci};
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic struct tc_action_ops act_mirred_ops;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic int tcf_mirred_init(struct net *net, struct nlattr *nla,
9362306a36Sopenharmony_ci			   struct nlattr *est, struct tc_action **a,
9462306a36Sopenharmony_ci			   struct tcf_proto *tp,
9562306a36Sopenharmony_ci			   u32 flags, struct netlink_ext_ack *extack)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	struct tc_action_net *tn = net_generic(net, act_mirred_ops.net_id);
9862306a36Sopenharmony_ci	bool bind = flags & TCA_ACT_FLAGS_BIND;
9962306a36Sopenharmony_ci	struct nlattr *tb[TCA_MIRRED_MAX + 1];
10062306a36Sopenharmony_ci	struct tcf_chain *goto_ch = NULL;
10162306a36Sopenharmony_ci	bool mac_header_xmit = false;
10262306a36Sopenharmony_ci	struct tc_mirred *parm;
10362306a36Sopenharmony_ci	struct tcf_mirred *m;
10462306a36Sopenharmony_ci	bool exists = false;
10562306a36Sopenharmony_ci	int ret, err;
10662306a36Sopenharmony_ci	u32 index;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	if (!nla) {
10962306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Mirred requires attributes to be passed");
11062306a36Sopenharmony_ci		return -EINVAL;
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci	ret = nla_parse_nested_deprecated(tb, TCA_MIRRED_MAX, nla,
11362306a36Sopenharmony_ci					  mirred_policy, extack);
11462306a36Sopenharmony_ci	if (ret < 0)
11562306a36Sopenharmony_ci		return ret;
11662306a36Sopenharmony_ci	if (!tb[TCA_MIRRED_PARMS]) {
11762306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Missing required mirred parameters");
11862306a36Sopenharmony_ci		return -EINVAL;
11962306a36Sopenharmony_ci	}
12062306a36Sopenharmony_ci	parm = nla_data(tb[TCA_MIRRED_PARMS]);
12162306a36Sopenharmony_ci	index = parm->index;
12262306a36Sopenharmony_ci	err = tcf_idr_check_alloc(tn, &index, a, bind);
12362306a36Sopenharmony_ci	if (err < 0)
12462306a36Sopenharmony_ci		return err;
12562306a36Sopenharmony_ci	exists = err;
12662306a36Sopenharmony_ci	if (exists && bind)
12762306a36Sopenharmony_ci		return 0;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	switch (parm->eaction) {
13062306a36Sopenharmony_ci	case TCA_EGRESS_MIRROR:
13162306a36Sopenharmony_ci	case TCA_EGRESS_REDIR:
13262306a36Sopenharmony_ci	case TCA_INGRESS_REDIR:
13362306a36Sopenharmony_ci	case TCA_INGRESS_MIRROR:
13462306a36Sopenharmony_ci		break;
13562306a36Sopenharmony_ci	default:
13662306a36Sopenharmony_ci		if (exists)
13762306a36Sopenharmony_ci			tcf_idr_release(*a, bind);
13862306a36Sopenharmony_ci		else
13962306a36Sopenharmony_ci			tcf_idr_cleanup(tn, index);
14062306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Unknown mirred option");
14162306a36Sopenharmony_ci		return -EINVAL;
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	if (!exists) {
14562306a36Sopenharmony_ci		if (!parm->ifindex) {
14662306a36Sopenharmony_ci			tcf_idr_cleanup(tn, index);
14762306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Specified device does not exist");
14862306a36Sopenharmony_ci			return -EINVAL;
14962306a36Sopenharmony_ci		}
15062306a36Sopenharmony_ci		ret = tcf_idr_create_from_flags(tn, index, est, a,
15162306a36Sopenharmony_ci						&act_mirred_ops, bind, flags);
15262306a36Sopenharmony_ci		if (ret) {
15362306a36Sopenharmony_ci			tcf_idr_cleanup(tn, index);
15462306a36Sopenharmony_ci			return ret;
15562306a36Sopenharmony_ci		}
15662306a36Sopenharmony_ci		ret = ACT_P_CREATED;
15762306a36Sopenharmony_ci	} else if (!(flags & TCA_ACT_FLAGS_REPLACE)) {
15862306a36Sopenharmony_ci		tcf_idr_release(*a, bind);
15962306a36Sopenharmony_ci		return -EEXIST;
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	m = to_mirred(*a);
16362306a36Sopenharmony_ci	if (ret == ACT_P_CREATED)
16462306a36Sopenharmony_ci		INIT_LIST_HEAD(&m->tcfm_list);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
16762306a36Sopenharmony_ci	if (err < 0)
16862306a36Sopenharmony_ci		goto release_idr;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	spin_lock_bh(&m->tcf_lock);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	if (parm->ifindex) {
17362306a36Sopenharmony_ci		struct net_device *odev, *ndev;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci		ndev = dev_get_by_index(net, parm->ifindex);
17662306a36Sopenharmony_ci		if (!ndev) {
17762306a36Sopenharmony_ci			spin_unlock_bh(&m->tcf_lock);
17862306a36Sopenharmony_ci			err = -ENODEV;
17962306a36Sopenharmony_ci			goto put_chain;
18062306a36Sopenharmony_ci		}
18162306a36Sopenharmony_ci		mac_header_xmit = dev_is_mac_header_xmit(ndev);
18262306a36Sopenharmony_ci		odev = rcu_replace_pointer(m->tcfm_dev, ndev,
18362306a36Sopenharmony_ci					  lockdep_is_held(&m->tcf_lock));
18462306a36Sopenharmony_ci		netdev_put(odev, &m->tcfm_dev_tracker);
18562306a36Sopenharmony_ci		netdev_tracker_alloc(ndev, &m->tcfm_dev_tracker, GFP_ATOMIC);
18662306a36Sopenharmony_ci		m->tcfm_mac_header_xmit = mac_header_xmit;
18762306a36Sopenharmony_ci	}
18862306a36Sopenharmony_ci	goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
18962306a36Sopenharmony_ci	m->tcfm_eaction = parm->eaction;
19062306a36Sopenharmony_ci	spin_unlock_bh(&m->tcf_lock);
19162306a36Sopenharmony_ci	if (goto_ch)
19262306a36Sopenharmony_ci		tcf_chain_put_by_act(goto_ch);
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	if (ret == ACT_P_CREATED) {
19562306a36Sopenharmony_ci		spin_lock(&mirred_list_lock);
19662306a36Sopenharmony_ci		list_add(&m->tcfm_list, &mirred_list);
19762306a36Sopenharmony_ci		spin_unlock(&mirred_list_lock);
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	return ret;
20162306a36Sopenharmony_ciput_chain:
20262306a36Sopenharmony_ci	if (goto_ch)
20362306a36Sopenharmony_ci		tcf_chain_put_by_act(goto_ch);
20462306a36Sopenharmony_cirelease_idr:
20562306a36Sopenharmony_ci	tcf_idr_release(*a, bind);
20662306a36Sopenharmony_ci	return err;
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic int
21062306a36Sopenharmony_citcf_mirred_forward(bool at_ingress, bool want_ingress, struct sk_buff *skb)
21162306a36Sopenharmony_ci{
21262306a36Sopenharmony_ci	int err;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	if (!want_ingress)
21562306a36Sopenharmony_ci		err = tcf_dev_queue_xmit(skb, dev_queue_xmit);
21662306a36Sopenharmony_ci	else if (!at_ingress)
21762306a36Sopenharmony_ci		err = netif_rx(skb);
21862306a36Sopenharmony_ci	else
21962306a36Sopenharmony_ci		err = netif_receive_skb(skb);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	return err;
22262306a36Sopenharmony_ci}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic int tcf_mirred_to_dev(struct sk_buff *skb, struct tcf_mirred *m,
22562306a36Sopenharmony_ci			     struct net_device *dev,
22662306a36Sopenharmony_ci			     const bool m_mac_header_xmit, int m_eaction,
22762306a36Sopenharmony_ci			     int retval)
22862306a36Sopenharmony_ci{
22962306a36Sopenharmony_ci	struct sk_buff *skb_to_send = skb;
23062306a36Sopenharmony_ci	bool want_ingress;
23162306a36Sopenharmony_ci	bool is_redirect;
23262306a36Sopenharmony_ci	bool expects_nh;
23362306a36Sopenharmony_ci	bool at_ingress;
23462306a36Sopenharmony_ci	bool dont_clone;
23562306a36Sopenharmony_ci	int mac_len;
23662306a36Sopenharmony_ci	bool at_nh;
23762306a36Sopenharmony_ci	int err;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	is_redirect = tcf_mirred_is_act_redirect(m_eaction);
24062306a36Sopenharmony_ci	if (unlikely(!(dev->flags & IFF_UP)) || !netif_carrier_ok(dev)) {
24162306a36Sopenharmony_ci		net_notice_ratelimited("tc mirred to Houston: device %s is down\n",
24262306a36Sopenharmony_ci				       dev->name);
24362306a36Sopenharmony_ci		goto err_cant_do;
24462306a36Sopenharmony_ci	}
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	/* we could easily avoid the clone only if called by ingress and clsact;
24762306a36Sopenharmony_ci	 * since we can't easily detect the clsact caller, skip clone only for
24862306a36Sopenharmony_ci	 * ingress - that covers the TC S/W datapath.
24962306a36Sopenharmony_ci	 */
25062306a36Sopenharmony_ci	at_ingress = skb_at_tc_ingress(skb);
25162306a36Sopenharmony_ci	dont_clone = skb_at_tc_ingress(skb) && is_redirect &&
25262306a36Sopenharmony_ci		tcf_mirred_can_reinsert(retval);
25362306a36Sopenharmony_ci	if (!dont_clone) {
25462306a36Sopenharmony_ci		skb_to_send = skb_clone(skb, GFP_ATOMIC);
25562306a36Sopenharmony_ci		if (!skb_to_send)
25662306a36Sopenharmony_ci			goto err_cant_do;
25762306a36Sopenharmony_ci	}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	want_ingress = tcf_mirred_act_wants_ingress(m_eaction);
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	/* All mirred/redirected skbs should clear previous ct info */
26262306a36Sopenharmony_ci	nf_reset_ct(skb_to_send);
26362306a36Sopenharmony_ci	if (want_ingress && !at_ingress) /* drop dst for egress -> ingress */
26462306a36Sopenharmony_ci		skb_dst_drop(skb_to_send);
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	expects_nh = want_ingress || !m_mac_header_xmit;
26762306a36Sopenharmony_ci	at_nh = skb->data == skb_network_header(skb);
26862306a36Sopenharmony_ci	if (at_nh != expects_nh) {
26962306a36Sopenharmony_ci		mac_len = at_ingress ? skb->mac_len :
27062306a36Sopenharmony_ci			  skb_network_offset(skb);
27162306a36Sopenharmony_ci		if (expects_nh) {
27262306a36Sopenharmony_ci			/* target device/action expect data at nh */
27362306a36Sopenharmony_ci			skb_pull_rcsum(skb_to_send, mac_len);
27462306a36Sopenharmony_ci		} else {
27562306a36Sopenharmony_ci			/* target device/action expect data at mac */
27662306a36Sopenharmony_ci			skb_push_rcsum(skb_to_send, mac_len);
27762306a36Sopenharmony_ci		}
27862306a36Sopenharmony_ci	}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	skb_to_send->skb_iif = skb->dev->ifindex;
28162306a36Sopenharmony_ci	skb_to_send->dev = dev;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	if (is_redirect) {
28462306a36Sopenharmony_ci		if (skb == skb_to_send)
28562306a36Sopenharmony_ci			retval = TC_ACT_CONSUMED;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci		skb_set_redirected(skb_to_send, skb_to_send->tc_at_ingress);
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci		err = tcf_mirred_forward(at_ingress, want_ingress, skb_to_send);
29062306a36Sopenharmony_ci	} else {
29162306a36Sopenharmony_ci		err = tcf_mirred_forward(at_ingress, want_ingress, skb_to_send);
29262306a36Sopenharmony_ci	}
29362306a36Sopenharmony_ci	if (err)
29462306a36Sopenharmony_ci		tcf_action_inc_overlimit_qstats(&m->common);
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	return retval;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cierr_cant_do:
29962306a36Sopenharmony_ci	if (is_redirect)
30062306a36Sopenharmony_ci		retval = TC_ACT_SHOT;
30162306a36Sopenharmony_ci	tcf_action_inc_overlimit_qstats(&m->common);
30262306a36Sopenharmony_ci	return retval;
30362306a36Sopenharmony_ci}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ciTC_INDIRECT_SCOPE int tcf_mirred_act(struct sk_buff *skb,
30662306a36Sopenharmony_ci				     const struct tc_action *a,
30762306a36Sopenharmony_ci				     struct tcf_result *res)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	struct tcf_mirred *m = to_mirred(a);
31062306a36Sopenharmony_ci	int retval = READ_ONCE(m->tcf_action);
31162306a36Sopenharmony_ci	unsigned int nest_level;
31262306a36Sopenharmony_ci	bool m_mac_header_xmit;
31362306a36Sopenharmony_ci	struct net_device *dev;
31462306a36Sopenharmony_ci	int m_eaction;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	nest_level = __this_cpu_inc_return(mirred_nest_level);
31762306a36Sopenharmony_ci	if (unlikely(nest_level > MIRRED_NEST_LIMIT)) {
31862306a36Sopenharmony_ci		net_warn_ratelimited("Packet exceeded mirred recursion limit on dev %s\n",
31962306a36Sopenharmony_ci				     netdev_name(skb->dev));
32062306a36Sopenharmony_ci		retval = TC_ACT_SHOT;
32162306a36Sopenharmony_ci		goto dec_nest_level;
32262306a36Sopenharmony_ci	}
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	tcf_lastuse_update(&m->tcf_tm);
32562306a36Sopenharmony_ci	tcf_action_update_bstats(&m->common, skb);
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	dev = rcu_dereference_bh(m->tcfm_dev);
32862306a36Sopenharmony_ci	if (unlikely(!dev)) {
32962306a36Sopenharmony_ci		pr_notice_once("tc mirred: target device is gone\n");
33062306a36Sopenharmony_ci		tcf_action_inc_overlimit_qstats(&m->common);
33162306a36Sopenharmony_ci		goto dec_nest_level;
33262306a36Sopenharmony_ci	}
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	m_mac_header_xmit = READ_ONCE(m->tcfm_mac_header_xmit);
33562306a36Sopenharmony_ci	m_eaction = READ_ONCE(m->tcfm_eaction);
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci	retval = tcf_mirred_to_dev(skb, m, dev, m_mac_header_xmit, m_eaction,
33862306a36Sopenharmony_ci				   retval);
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_cidec_nest_level:
34162306a36Sopenharmony_ci	__this_cpu_dec(mirred_nest_level);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	return retval;
34462306a36Sopenharmony_ci}
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_cistatic void tcf_stats_update(struct tc_action *a, u64 bytes, u64 packets,
34762306a36Sopenharmony_ci			     u64 drops, u64 lastuse, bool hw)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci	struct tcf_mirred *m = to_mirred(a);
35062306a36Sopenharmony_ci	struct tcf_t *tm = &m->tcf_tm;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	tcf_action_update_stats(a, bytes, packets, drops, hw);
35362306a36Sopenharmony_ci	tm->lastuse = max_t(u64, tm->lastuse, lastuse);
35462306a36Sopenharmony_ci}
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_cistatic int tcf_mirred_dump(struct sk_buff *skb, struct tc_action *a, int bind,
35762306a36Sopenharmony_ci			   int ref)
35862306a36Sopenharmony_ci{
35962306a36Sopenharmony_ci	unsigned char *b = skb_tail_pointer(skb);
36062306a36Sopenharmony_ci	struct tcf_mirred *m = to_mirred(a);
36162306a36Sopenharmony_ci	struct tc_mirred opt = {
36262306a36Sopenharmony_ci		.index   = m->tcf_index,
36362306a36Sopenharmony_ci		.refcnt  = refcount_read(&m->tcf_refcnt) - ref,
36462306a36Sopenharmony_ci		.bindcnt = atomic_read(&m->tcf_bindcnt) - bind,
36562306a36Sopenharmony_ci	};
36662306a36Sopenharmony_ci	struct net_device *dev;
36762306a36Sopenharmony_ci	struct tcf_t t;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	spin_lock_bh(&m->tcf_lock);
37062306a36Sopenharmony_ci	opt.action = m->tcf_action;
37162306a36Sopenharmony_ci	opt.eaction = m->tcfm_eaction;
37262306a36Sopenharmony_ci	dev = tcf_mirred_dev_dereference(m);
37362306a36Sopenharmony_ci	if (dev)
37462306a36Sopenharmony_ci		opt.ifindex = dev->ifindex;
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	if (nla_put(skb, TCA_MIRRED_PARMS, sizeof(opt), &opt))
37762306a36Sopenharmony_ci		goto nla_put_failure;
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	tcf_tm_dump(&t, &m->tcf_tm);
38062306a36Sopenharmony_ci	if (nla_put_64bit(skb, TCA_MIRRED_TM, sizeof(t), &t, TCA_MIRRED_PAD))
38162306a36Sopenharmony_ci		goto nla_put_failure;
38262306a36Sopenharmony_ci	spin_unlock_bh(&m->tcf_lock);
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	return skb->len;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_cinla_put_failure:
38762306a36Sopenharmony_ci	spin_unlock_bh(&m->tcf_lock);
38862306a36Sopenharmony_ci	nlmsg_trim(skb, b);
38962306a36Sopenharmony_ci	return -1;
39062306a36Sopenharmony_ci}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_cistatic int mirred_device_event(struct notifier_block *unused,
39362306a36Sopenharmony_ci			       unsigned long event, void *ptr)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
39662306a36Sopenharmony_ci	struct tcf_mirred *m;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	ASSERT_RTNL();
39962306a36Sopenharmony_ci	if (event == NETDEV_UNREGISTER) {
40062306a36Sopenharmony_ci		spin_lock(&mirred_list_lock);
40162306a36Sopenharmony_ci		list_for_each_entry(m, &mirred_list, tcfm_list) {
40262306a36Sopenharmony_ci			spin_lock_bh(&m->tcf_lock);
40362306a36Sopenharmony_ci			if (tcf_mirred_dev_dereference(m) == dev) {
40462306a36Sopenharmony_ci				netdev_put(dev, &m->tcfm_dev_tracker);
40562306a36Sopenharmony_ci				/* Note : no rcu grace period necessary, as
40662306a36Sopenharmony_ci				 * net_device are already rcu protected.
40762306a36Sopenharmony_ci				 */
40862306a36Sopenharmony_ci				RCU_INIT_POINTER(m->tcfm_dev, NULL);
40962306a36Sopenharmony_ci			}
41062306a36Sopenharmony_ci			spin_unlock_bh(&m->tcf_lock);
41162306a36Sopenharmony_ci		}
41262306a36Sopenharmony_ci		spin_unlock(&mirred_list_lock);
41362306a36Sopenharmony_ci	}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	return NOTIFY_DONE;
41662306a36Sopenharmony_ci}
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_cistatic struct notifier_block mirred_device_notifier = {
41962306a36Sopenharmony_ci	.notifier_call = mirred_device_event,
42062306a36Sopenharmony_ci};
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_cistatic void tcf_mirred_dev_put(void *priv)
42362306a36Sopenharmony_ci{
42462306a36Sopenharmony_ci	struct net_device *dev = priv;
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	dev_put(dev);
42762306a36Sopenharmony_ci}
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_cistatic struct net_device *
43062306a36Sopenharmony_citcf_mirred_get_dev(const struct tc_action *a,
43162306a36Sopenharmony_ci		   tc_action_priv_destructor *destructor)
43262306a36Sopenharmony_ci{
43362306a36Sopenharmony_ci	struct tcf_mirred *m = to_mirred(a);
43462306a36Sopenharmony_ci	struct net_device *dev;
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	rcu_read_lock();
43762306a36Sopenharmony_ci	dev = rcu_dereference(m->tcfm_dev);
43862306a36Sopenharmony_ci	if (dev) {
43962306a36Sopenharmony_ci		dev_hold(dev);
44062306a36Sopenharmony_ci		*destructor = tcf_mirred_dev_put;
44162306a36Sopenharmony_ci	}
44262306a36Sopenharmony_ci	rcu_read_unlock();
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	return dev;
44562306a36Sopenharmony_ci}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_cistatic size_t tcf_mirred_get_fill_size(const struct tc_action *act)
44862306a36Sopenharmony_ci{
44962306a36Sopenharmony_ci	return nla_total_size(sizeof(struct tc_mirred));
45062306a36Sopenharmony_ci}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_cistatic void tcf_offload_mirred_get_dev(struct flow_action_entry *entry,
45362306a36Sopenharmony_ci				       const struct tc_action *act)
45462306a36Sopenharmony_ci{
45562306a36Sopenharmony_ci	entry->dev = act->ops->get_dev(act, &entry->destructor);
45662306a36Sopenharmony_ci	if (!entry->dev)
45762306a36Sopenharmony_ci		return;
45862306a36Sopenharmony_ci	entry->destructor_priv = entry->dev;
45962306a36Sopenharmony_ci}
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_cistatic int tcf_mirred_offload_act_setup(struct tc_action *act, void *entry_data,
46262306a36Sopenharmony_ci					u32 *index_inc, bool bind,
46362306a36Sopenharmony_ci					struct netlink_ext_ack *extack)
46462306a36Sopenharmony_ci{
46562306a36Sopenharmony_ci	if (bind) {
46662306a36Sopenharmony_ci		struct flow_action_entry *entry = entry_data;
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci		if (is_tcf_mirred_egress_redirect(act)) {
46962306a36Sopenharmony_ci			entry->id = FLOW_ACTION_REDIRECT;
47062306a36Sopenharmony_ci			tcf_offload_mirred_get_dev(entry, act);
47162306a36Sopenharmony_ci		} else if (is_tcf_mirred_egress_mirror(act)) {
47262306a36Sopenharmony_ci			entry->id = FLOW_ACTION_MIRRED;
47362306a36Sopenharmony_ci			tcf_offload_mirred_get_dev(entry, act);
47462306a36Sopenharmony_ci		} else if (is_tcf_mirred_ingress_redirect(act)) {
47562306a36Sopenharmony_ci			entry->id = FLOW_ACTION_REDIRECT_INGRESS;
47662306a36Sopenharmony_ci			tcf_offload_mirred_get_dev(entry, act);
47762306a36Sopenharmony_ci		} else if (is_tcf_mirred_ingress_mirror(act)) {
47862306a36Sopenharmony_ci			entry->id = FLOW_ACTION_MIRRED_INGRESS;
47962306a36Sopenharmony_ci			tcf_offload_mirred_get_dev(entry, act);
48062306a36Sopenharmony_ci		} else {
48162306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Unsupported mirred offload");
48262306a36Sopenharmony_ci			return -EOPNOTSUPP;
48362306a36Sopenharmony_ci		}
48462306a36Sopenharmony_ci		*index_inc = 1;
48562306a36Sopenharmony_ci	} else {
48662306a36Sopenharmony_ci		struct flow_offload_action *fl_action = entry_data;
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci		if (is_tcf_mirred_egress_redirect(act))
48962306a36Sopenharmony_ci			fl_action->id = FLOW_ACTION_REDIRECT;
49062306a36Sopenharmony_ci		else if (is_tcf_mirred_egress_mirror(act))
49162306a36Sopenharmony_ci			fl_action->id = FLOW_ACTION_MIRRED;
49262306a36Sopenharmony_ci		else if (is_tcf_mirred_ingress_redirect(act))
49362306a36Sopenharmony_ci			fl_action->id = FLOW_ACTION_REDIRECT_INGRESS;
49462306a36Sopenharmony_ci		else if (is_tcf_mirred_ingress_mirror(act))
49562306a36Sopenharmony_ci			fl_action->id = FLOW_ACTION_MIRRED_INGRESS;
49662306a36Sopenharmony_ci		else
49762306a36Sopenharmony_ci			return -EOPNOTSUPP;
49862306a36Sopenharmony_ci	}
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	return 0;
50162306a36Sopenharmony_ci}
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_cistatic struct tc_action_ops act_mirred_ops = {
50462306a36Sopenharmony_ci	.kind		=	"mirred",
50562306a36Sopenharmony_ci	.id		=	TCA_ID_MIRRED,
50662306a36Sopenharmony_ci	.owner		=	THIS_MODULE,
50762306a36Sopenharmony_ci	.act		=	tcf_mirred_act,
50862306a36Sopenharmony_ci	.stats_update	=	tcf_stats_update,
50962306a36Sopenharmony_ci	.dump		=	tcf_mirred_dump,
51062306a36Sopenharmony_ci	.cleanup	=	tcf_mirred_release,
51162306a36Sopenharmony_ci	.init		=	tcf_mirred_init,
51262306a36Sopenharmony_ci	.get_fill_size	=	tcf_mirred_get_fill_size,
51362306a36Sopenharmony_ci	.offload_act_setup =	tcf_mirred_offload_act_setup,
51462306a36Sopenharmony_ci	.size		=	sizeof(struct tcf_mirred),
51562306a36Sopenharmony_ci	.get_dev	=	tcf_mirred_get_dev,
51662306a36Sopenharmony_ci};
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_cistatic __net_init int mirred_init_net(struct net *net)
51962306a36Sopenharmony_ci{
52062306a36Sopenharmony_ci	struct tc_action_net *tn = net_generic(net, act_mirred_ops.net_id);
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	return tc_action_net_init(net, tn, &act_mirred_ops);
52362306a36Sopenharmony_ci}
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_cistatic void __net_exit mirred_exit_net(struct list_head *net_list)
52662306a36Sopenharmony_ci{
52762306a36Sopenharmony_ci	tc_action_net_exit(net_list, act_mirred_ops.net_id);
52862306a36Sopenharmony_ci}
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_cistatic struct pernet_operations mirred_net_ops = {
53162306a36Sopenharmony_ci	.init = mirred_init_net,
53262306a36Sopenharmony_ci	.exit_batch = mirred_exit_net,
53362306a36Sopenharmony_ci	.id   = &act_mirred_ops.net_id,
53462306a36Sopenharmony_ci	.size = sizeof(struct tc_action_net),
53562306a36Sopenharmony_ci};
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ciMODULE_AUTHOR("Jamal Hadi Salim(2002)");
53862306a36Sopenharmony_ciMODULE_DESCRIPTION("Device Mirror/redirect actions");
53962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_cistatic int __init mirred_init_module(void)
54262306a36Sopenharmony_ci{
54362306a36Sopenharmony_ci	int err = register_netdevice_notifier(&mirred_device_notifier);
54462306a36Sopenharmony_ci	if (err)
54562306a36Sopenharmony_ci		return err;
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_ci	pr_info("Mirror/redirect action on\n");
54862306a36Sopenharmony_ci	err = tcf_register_action(&act_mirred_ops, &mirred_net_ops);
54962306a36Sopenharmony_ci	if (err)
55062306a36Sopenharmony_ci		unregister_netdevice_notifier(&mirred_device_notifier);
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	return err;
55362306a36Sopenharmony_ci}
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_cistatic void __exit mirred_cleanup_module(void)
55662306a36Sopenharmony_ci{
55762306a36Sopenharmony_ci	tcf_unregister_action(&act_mirred_ops, &mirred_net_ops);
55862306a36Sopenharmony_ci	unregister_netdevice_notifier(&mirred_device_notifier);
55962306a36Sopenharmony_ci}
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_cimodule_init(mirred_init_module);
56262306a36Sopenharmony_cimodule_exit(mirred_cleanup_module);
563