xref: /openbmc/linux/net/sched/act_mirred.c (revision 96d3c5a7d20ec546e44695983fe0508c6f904248)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
24bba3925SPatrick McHardy /*
30c6965ddSJiri Pirko  * net/sched/act_mirred.c	packet mirroring and redirect actions
44bba3925SPatrick McHardy  *
54bba3925SPatrick McHardy  * Authors:	Jamal Hadi Salim (2002-4)
64bba3925SPatrick McHardy  *
74bba3925SPatrick McHardy  * TODO: Add ingress support (and socket redirect support)
84bba3925SPatrick McHardy  */
94bba3925SPatrick McHardy 
104bba3925SPatrick McHardy #include <linux/types.h>
114bba3925SPatrick McHardy #include <linux/kernel.h>
124bba3925SPatrick McHardy #include <linux/string.h>
134bba3925SPatrick McHardy #include <linux/errno.h>
144bba3925SPatrick McHardy #include <linux/skbuff.h>
154bba3925SPatrick McHardy #include <linux/rtnetlink.h>
164bba3925SPatrick McHardy #include <linux/module.h>
174bba3925SPatrick McHardy #include <linux/init.h>
185a0e3ad6STejun Heo #include <linux/gfp.h>
19c491680fSDaniel Borkmann #include <linux/if_arp.h>
20881d966bSEric W. Biederman #include <net/net_namespace.h>
21dc5fc579SArnaldo Carvalho de Melo #include <net/netlink.h>
22f799ada6SXin Long #include <net/dst.h>
234bba3925SPatrick McHardy #include <net/pkt_sched.h>
24e5cf1bafSPaolo Abeni #include <net/pkt_cls.h>
254bba3925SPatrick McHardy #include <linux/tc_act/tc_mirred.h>
264bba3925SPatrick McHardy #include <net/tc_act/tc_mirred.h>
27871cf386SPedro Tammela #include <net/tc_wrapper.h>
284bba3925SPatrick McHardy 
293b87956eSstephen hemminger static LIST_HEAD(mirred_list);
304e232818SVlad Buslov static DEFINE_SPINLOCK(mirred_list_lock);
314bba3925SPatrick McHardy 
3278dcdffeSDavide Caratti #define MIRRED_NEST_LIMIT    4
3378dcdffeSDavide Caratti static DEFINE_PER_CPU(unsigned int, mirred_nest_level);
34e2ca070fSJohn Hurley 
tcf_mirred_is_act_redirect(int action)3553592b36SShmulik Ladkani static bool tcf_mirred_is_act_redirect(int action)
3653592b36SShmulik Ladkani {
3753592b36SShmulik Ladkani 	return action == TCA_EGRESS_REDIR || action == TCA_INGRESS_REDIR;
3853592b36SShmulik Ladkani }
3953592b36SShmulik Ladkani 
tcf_mirred_act_wants_ingress(int action)408dc07fdbSWillem de Bruijn static bool tcf_mirred_act_wants_ingress(int action)
4153592b36SShmulik Ladkani {
4253592b36SShmulik Ladkani 	switch (action) {
4353592b36SShmulik Ladkani 	case TCA_EGRESS_REDIR:
4453592b36SShmulik Ladkani 	case TCA_EGRESS_MIRROR:
458dc07fdbSWillem de Bruijn 		return false;
4653592b36SShmulik Ladkani 	case TCA_INGRESS_REDIR:
4753592b36SShmulik Ladkani 	case TCA_INGRESS_MIRROR:
488dc07fdbSWillem de Bruijn 		return true;
4953592b36SShmulik Ladkani 	default:
5053592b36SShmulik Ladkani 		BUG();
5153592b36SShmulik Ladkani 	}
5253592b36SShmulik Ladkani }
5353592b36SShmulik Ladkani 
tcf_mirred_can_reinsert(int action)54e5cf1bafSPaolo Abeni static bool tcf_mirred_can_reinsert(int action)
55e5cf1bafSPaolo Abeni {
56e5cf1bafSPaolo Abeni 	switch (action) {
57e5cf1bafSPaolo Abeni 	case TC_ACT_SHOT:
58e5cf1bafSPaolo Abeni 	case TC_ACT_STOLEN:
59e5cf1bafSPaolo Abeni 	case TC_ACT_QUEUED:
60e5cf1bafSPaolo Abeni 	case TC_ACT_TRAP:
61e5cf1bafSPaolo Abeni 		return true;
62e5cf1bafSPaolo Abeni 	}
63e5cf1bafSPaolo Abeni 	return false;
64e5cf1bafSPaolo Abeni }
65e5cf1bafSPaolo Abeni 
tcf_mirred_dev_dereference(struct tcf_mirred * m)664e232818SVlad Buslov static struct net_device *tcf_mirred_dev_dereference(struct tcf_mirred *m)
674e232818SVlad Buslov {
684e232818SVlad Buslov 	return rcu_dereference_protected(m->tcfm_dev,
694e232818SVlad Buslov 					 lockdep_is_held(&m->tcf_lock));
704e232818SVlad Buslov }
714e232818SVlad Buslov 
tcf_mirred_release(struct tc_action * a)729a63b255SCong Wang static void tcf_mirred_release(struct tc_action *a)
734bba3925SPatrick McHardy {
7486062033SWANG Cong 	struct tcf_mirred *m = to_mirred(a);
75dc327f89SWANG Cong 	struct net_device *dev;
762ee22a90SEric Dumazet 
774e232818SVlad Buslov 	spin_lock(&mirred_list_lock);
783b87956eSstephen hemminger 	list_del(&m->tcfm_list);
794e232818SVlad Buslov 	spin_unlock(&mirred_list_lock);
804e232818SVlad Buslov 
814e232818SVlad Buslov 	/* last reference to action, no need to lock */
824e232818SVlad Buslov 	dev = rcu_dereference_protected(m->tcfm_dev, 1);
83d62607c3SJakub Kicinski 	netdev_put(dev, &m->tcfm_dev_tracker);
844bba3925SPatrick McHardy }
854bba3925SPatrick McHardy 
8653b2bf3fSPatrick McHardy static const struct nla_policy mirred_policy[TCA_MIRRED_MAX + 1] = {
8753b2bf3fSPatrick McHardy 	[TCA_MIRRED_PARMS]	= { .len = sizeof(struct tc_mirred) },
8853b2bf3fSPatrick McHardy };
8953b2bf3fSPatrick McHardy 
90a85a970aSWANG Cong static struct tc_action_ops act_mirred_ops;
91ddf97ccdSWANG Cong 
tcf_mirred_init(struct net * net,struct nlattr * nla,struct nlattr * est,struct tc_action ** a,struct tcf_proto * tp,u32 flags,struct netlink_ext_ack * extack)92c1b52739SBenjamin LaHaise static int tcf_mirred_init(struct net *net, struct nlattr *nla,
93789871bbSVlad Buslov 			   struct nlattr *est, struct tc_action **a,
9485d0966fSDavide Caratti 			   struct tcf_proto *tp,
95abbb0d33SVlad Buslov 			   u32 flags, struct netlink_ext_ack *extack)
964bba3925SPatrick McHardy {
97acd0a7abSZhengchao Shao 	struct tc_action_net *tn = net_generic(net, act_mirred_ops.net_id);
98695176bfSCong Wang 	bool bind = flags & TCA_ACT_FLAGS_BIND;
997ba699c6SPatrick McHardy 	struct nlattr *tb[TCA_MIRRED_MAX + 1];
100ff9721d3SDavide Caratti 	struct tcf_chain *goto_ch = NULL;
10116577923SShmulik Ladkani 	bool mac_header_xmit = false;
1024bba3925SPatrick McHardy 	struct tc_mirred *parm;
103e9ce1cd3SDavid S. Miller 	struct tcf_mirred *m;
104b2313077SWANG Cong 	bool exists = false;
1050190c1d4SVlad Buslov 	int ret, err;
1067be8ef2cSDmytro Linkin 	u32 index;
1074bba3925SPatrick McHardy 
1081d4760c7SAlexander Aring 	if (!nla) {
1091d4760c7SAlexander Aring 		NL_SET_ERR_MSG_MOD(extack, "Mirred requires attributes to be passed");
1104bba3925SPatrick McHardy 		return -EINVAL;
1111d4760c7SAlexander Aring 	}
1128cb08174SJohannes Berg 	ret = nla_parse_nested_deprecated(tb, TCA_MIRRED_MAX, nla,
1138cb08174SJohannes Berg 					  mirred_policy, extack);
114b76965e0SChangli Gao 	if (ret < 0)
115b76965e0SChangli Gao 		return ret;
1161d4760c7SAlexander Aring 	if (!tb[TCA_MIRRED_PARMS]) {
1171d4760c7SAlexander Aring 		NL_SET_ERR_MSG_MOD(extack, "Missing required mirred parameters");
1184bba3925SPatrick McHardy 		return -EINVAL;
1191d4760c7SAlexander Aring 	}
1207ba699c6SPatrick McHardy 	parm = nla_data(tb[TCA_MIRRED_PARMS]);
1217be8ef2cSDmytro Linkin 	index = parm->index;
1227be8ef2cSDmytro Linkin 	err = tcf_idr_check_alloc(tn, &index, a, bind);
1230190c1d4SVlad Buslov 	if (err < 0)
1240190c1d4SVlad Buslov 		return err;
1250190c1d4SVlad Buslov 	exists = err;
12687dfbdc6SJamal Hadi Salim 	if (exists && bind)
12787dfbdc6SJamal Hadi Salim 		return 0;
12887dfbdc6SJamal Hadi Salim 
129b76965e0SChangli Gao 	switch (parm->eaction) {
130b76965e0SChangli Gao 	case TCA_EGRESS_MIRROR:
131b76965e0SChangli Gao 	case TCA_EGRESS_REDIR:
13253592b36SShmulik Ladkani 	case TCA_INGRESS_REDIR:
13353592b36SShmulik Ladkani 	case TCA_INGRESS_MIRROR:
134b76965e0SChangli Gao 		break;
135b76965e0SChangli Gao 	default:
13687dfbdc6SJamal Hadi Salim 		if (exists)
13765a206c0SChris Mi 			tcf_idr_release(*a, bind);
1380190c1d4SVlad Buslov 		else
1397be8ef2cSDmytro Linkin 			tcf_idr_cleanup(tn, index);
1401d4760c7SAlexander Aring 		NL_SET_ERR_MSG_MOD(extack, "Unknown mirred option");
141b76965e0SChangli Gao 		return -EINVAL;
142b76965e0SChangli Gao 	}
1434bba3925SPatrick McHardy 
14487dfbdc6SJamal Hadi Salim 	if (!exists) {
1454e232818SVlad Buslov 		if (!parm->ifindex) {
1467be8ef2cSDmytro Linkin 			tcf_idr_cleanup(tn, index);
1471d4760c7SAlexander Aring 			NL_SET_ERR_MSG_MOD(extack, "Specified device does not exist");
1484bba3925SPatrick McHardy 			return -EINVAL;
1491d4760c7SAlexander Aring 		}
150e3822678SVlad Buslov 		ret = tcf_idr_create_from_flags(tn, index, est, a,
151e3822678SVlad Buslov 						&act_mirred_ops, bind, flags);
1520190c1d4SVlad Buslov 		if (ret) {
1537be8ef2cSDmytro Linkin 			tcf_idr_cleanup(tn, index);
15486062033SWANG Cong 			return ret;
1550190c1d4SVlad Buslov 		}
1564bba3925SPatrick McHardy 		ret = ACT_P_CREATED;
157695176bfSCong Wang 	} else if (!(flags & TCA_ACT_FLAGS_REPLACE)) {
15865a206c0SChris Mi 		tcf_idr_release(*a, bind);
1594bba3925SPatrick McHardy 		return -EEXIST;
1604bba3925SPatrick McHardy 	}
161064c5d68SJohn Hurley 
162064c5d68SJohn Hurley 	m = to_mirred(*a);
163064c5d68SJohn Hurley 	if (ret == ACT_P_CREATED)
164064c5d68SJohn Hurley 		INIT_LIST_HEAD(&m->tcfm_list);
165064c5d68SJohn Hurley 
166ff9721d3SDavide Caratti 	err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
167ff9721d3SDavide Caratti 	if (err < 0)
168ff9721d3SDavide Caratti 		goto release_idr;
169ff9721d3SDavide Caratti 
170653cd284SVlad Buslov 	spin_lock_bh(&m->tcf_lock);
1714e232818SVlad Buslov 
1724e232818SVlad Buslov 	if (parm->ifindex) {
173ada066b2SEric Dumazet 		struct net_device *odev, *ndev;
174ada066b2SEric Dumazet 
175ada066b2SEric Dumazet 		ndev = dev_get_by_index(net, parm->ifindex);
176ada066b2SEric Dumazet 		if (!ndev) {
177653cd284SVlad Buslov 			spin_unlock_bh(&m->tcf_lock);
178ff9721d3SDavide Caratti 			err = -ENODEV;
179ff9721d3SDavide Caratti 			goto put_chain;
1804e232818SVlad Buslov 		}
181ada066b2SEric Dumazet 		mac_header_xmit = dev_is_mac_header_xmit(ndev);
182ada066b2SEric Dumazet 		odev = rcu_replace_pointer(m->tcfm_dev, ndev,
1834e232818SVlad Buslov 					  lockdep_is_held(&m->tcf_lock));
184d62607c3SJakub Kicinski 		netdev_put(odev, &m->tcfm_dev_tracker);
185ada066b2SEric Dumazet 		netdev_tracker_alloc(ndev, &m->tcfm_dev_tracker, GFP_ATOMIC);
18616577923SShmulik Ladkani 		m->tcfm_mac_header_xmit = mac_header_xmit;
1874bba3925SPatrick McHardy 	}
188ff9721d3SDavide Caratti 	goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
189ff9721d3SDavide Caratti 	m->tcfm_eaction = parm->eaction;
190653cd284SVlad Buslov 	spin_unlock_bh(&m->tcf_lock);
191ff9721d3SDavide Caratti 	if (goto_ch)
192ff9721d3SDavide Caratti 		tcf_chain_put_by_act(goto_ch);
1932ee22a90SEric Dumazet 
1943b87956eSstephen hemminger 	if (ret == ACT_P_CREATED) {
1954e232818SVlad Buslov 		spin_lock(&mirred_list_lock);
1963b87956eSstephen hemminger 		list_add(&m->tcfm_list, &mirred_list);
1974e232818SVlad Buslov 		spin_unlock(&mirred_list_lock);
1983b87956eSstephen hemminger 	}
1994bba3925SPatrick McHardy 
2004bba3925SPatrick McHardy 	return ret;
201ff9721d3SDavide Caratti put_chain:
202ff9721d3SDavide Caratti 	if (goto_ch)
203ff9721d3SDavide Caratti 		tcf_chain_put_by_act(goto_ch);
204ff9721d3SDavide Caratti release_idr:
205ff9721d3SDavide Caratti 	tcf_idr_release(*a, bind);
206ff9721d3SDavide Caratti 	return err;
2074bba3925SPatrick McHardy }
2084bba3925SPatrick McHardy 
2097c787888SJakub Kicinski static int
tcf_mirred_forward(bool at_ingress,bool want_ingress,struct sk_buff * skb)2107c787888SJakub Kicinski tcf_mirred_forward(bool at_ingress, bool want_ingress, struct sk_buff *skb)
211fa6d6399Swenxu {
212fa6d6399Swenxu 	int err;
213fa6d6399Swenxu 
214fa6d6399Swenxu 	if (!want_ingress)
215c129412fSwenxu 		err = tcf_dev_queue_xmit(skb, dev_queue_xmit);
2167c787888SJakub Kicinski 	else if (!at_ingress)
217ca22da2fSDavide Caratti 		err = netif_rx(skb);
218fa6d6399Swenxu 	else
219fa6d6399Swenxu 		err = netif_receive_skb(skb);
220fa6d6399Swenxu 
221fa6d6399Swenxu 	return err;
222fa6d6399Swenxu }
223fa6d6399Swenxu 
tcf_mirred_to_dev(struct sk_buff * skb,struct tcf_mirred * m,struct net_device * dev,const bool m_mac_header_xmit,int m_eaction,int retval)22473db191dSVictor Nogueira static int tcf_mirred_to_dev(struct sk_buff *skb, struct tcf_mirred *m,
22573db191dSVictor Nogueira 			     struct net_device *dev,
22673db191dSVictor Nogueira 			     const bool m_mac_header_xmit, int m_eaction,
22773db191dSVictor Nogueira 			     int retval)
2284bba3925SPatrick McHardy {
22973db191dSVictor Nogueira 	struct sk_buff *skb_to_send = skb;
230e5cf1bafSPaolo Abeni 	bool want_ingress;
231e5cf1bafSPaolo Abeni 	bool is_redirect;
23270cf3dc7SShmulik Ladkani 	bool expects_nh;
233f799ada6SXin Long 	bool at_ingress;
23473db191dSVictor Nogueira 	bool dont_clone;
23553592b36SShmulik Ladkani 	int mac_len;
23670cf3dc7SShmulik Ladkani 	bool at_nh;
23773db191dSVictor Nogueira 	int err;
2384bba3925SPatrick McHardy 
23973db191dSVictor Nogueira 	is_redirect = tcf_mirred_is_act_redirect(m_eaction);
240526f28bdSVictor Nogueira 	if (unlikely(!(dev->flags & IFF_UP)) || !netif_carrier_ok(dev)) {
241e87cc472SJoe Perches 		net_notice_ratelimited("tc mirred to Houston: device %s is down\n",
242feed1f17SChangli Gao 				       dev->name);
243*28cdbbd3SJakub Kicinski 		goto err_cant_do;
244feed1f17SChangli Gao 	}
245feed1f17SChangli Gao 
246e5cf1bafSPaolo Abeni 	/* we could easily avoid the clone only if called by ingress and clsact;
247e5cf1bafSPaolo Abeni 	 * since we can't easily detect the clsact caller, skip clone only for
248e5cf1bafSPaolo Abeni 	 * ingress - that covers the TC S/W datapath.
249e5cf1bafSPaolo Abeni 	 */
250f799ada6SXin Long 	at_ingress = skb_at_tc_ingress(skb);
25173db191dSVictor Nogueira 	dont_clone = skb_at_tc_ingress(skb) && is_redirect &&
252e5cf1bafSPaolo Abeni 		tcf_mirred_can_reinsert(retval);
25373db191dSVictor Nogueira 	if (!dont_clone) {
25473db191dSVictor Nogueira 		skb_to_send = skb_clone(skb, GFP_ATOMIC);
255*28cdbbd3SJakub Kicinski 		if (!skb_to_send)
256*28cdbbd3SJakub Kicinski 			goto err_cant_do;
25773db191dSVictor Nogueira 	}
258feed1f17SChangli Gao 
259f799ada6SXin Long 	want_ingress = tcf_mirred_act_wants_ingress(m_eaction);
260f799ada6SXin Long 
261d09c548dSHangbin Liu 	/* All mirred/redirected skbs should clear previous ct info */
26273db191dSVictor Nogueira 	nf_reset_ct(skb_to_send);
263f799ada6SXin Long 	if (want_ingress && !at_ingress) /* drop dst for egress -> ingress */
26473db191dSVictor Nogueira 		skb_dst_drop(skb_to_send);
26570cf3dc7SShmulik Ladkani 
26670cf3dc7SShmulik Ladkani 	expects_nh = want_ingress || !m_mac_header_xmit;
26770cf3dc7SShmulik Ladkani 	at_nh = skb->data == skb_network_header(skb);
26870cf3dc7SShmulik Ladkani 	if (at_nh != expects_nh) {
26973db191dSVictor Nogueira 		mac_len = at_ingress ? skb->mac_len :
270b3be9488SEric Dumazet 			  skb_network_offset(skb);
27170cf3dc7SShmulik Ladkani 		if (expects_nh) {
27270cf3dc7SShmulik Ladkani 			/* target device/action expect data at nh */
27373db191dSVictor Nogueira 			skb_pull_rcsum(skb_to_send, mac_len);
27453592b36SShmulik Ladkani 		} else {
27570cf3dc7SShmulik Ladkani 			/* target device/action expect data at mac */
27673db191dSVictor Nogueira 			skb_push_rcsum(skb_to_send, mac_len);
277feed1f17SChangli Gao 		}
27853592b36SShmulik Ladkani 	}
2794bba3925SPatrick McHardy 
28073db191dSVictor Nogueira 	skb_to_send->skb_iif = skb->dev->ifindex;
28173db191dSVictor Nogueira 	skb_to_send->dev = dev;
282e5cf1bafSPaolo Abeni 
283e5cf1bafSPaolo Abeni 	if (is_redirect) {
28473db191dSVictor Nogueira 		if (skb == skb_to_send)
28573db191dSVictor Nogueira 			retval = TC_ACT_CONSUMED;
2862c64605bSPablo Neira Ayuso 
28773db191dSVictor Nogueira 		skb_set_redirected(skb_to_send, skb_to_send->tc_at_ingress);
28873db191dSVictor Nogueira 
2897c787888SJakub Kicinski 		err = tcf_mirred_forward(at_ingress, want_ingress, skb_to_send);
29073db191dSVictor Nogueira 	} else {
2917c787888SJakub Kicinski 		err = tcf_mirred_forward(at_ingress, want_ingress, skb_to_send);
292e5cf1bafSPaolo Abeni 	}
293*28cdbbd3SJakub Kicinski 	if (err)
29426b537a8SVlad Buslov 		tcf_action_inc_overlimit_qstats(&m->common);
295*28cdbbd3SJakub Kicinski 
296*28cdbbd3SJakub Kicinski 	return retval;
297*28cdbbd3SJakub Kicinski 
298*28cdbbd3SJakub Kicinski err_cant_do:
29973db191dSVictor Nogueira 	if (is_redirect)
300feed1f17SChangli Gao 		retval = TC_ACT_SHOT;
301*28cdbbd3SJakub Kicinski 	tcf_action_inc_overlimit_qstats(&m->common);
30273db191dSVictor Nogueira 	return retval;
30373db191dSVictor Nogueira }
30473db191dSVictor Nogueira 
tcf_mirred_act(struct sk_buff * skb,const struct tc_action * a,struct tcf_result * res)30573db191dSVictor Nogueira TC_INDIRECT_SCOPE int tcf_mirred_act(struct sk_buff *skb,
30673db191dSVictor Nogueira 				     const struct tc_action *a,
30773db191dSVictor Nogueira 				     struct tcf_result *res)
30873db191dSVictor Nogueira {
30973db191dSVictor Nogueira 	struct tcf_mirred *m = to_mirred(a);
31073db191dSVictor Nogueira 	int retval = READ_ONCE(m->tcf_action);
31173db191dSVictor Nogueira 	unsigned int nest_level;
31273db191dSVictor Nogueira 	bool m_mac_header_xmit;
31373db191dSVictor Nogueira 	struct net_device *dev;
31473db191dSVictor Nogueira 	int m_eaction;
31573db191dSVictor Nogueira 
31673db191dSVictor Nogueira 	nest_level = __this_cpu_inc_return(mirred_nest_level);
31773db191dSVictor Nogueira 	if (unlikely(nest_level > MIRRED_NEST_LIMIT)) {
31873db191dSVictor Nogueira 		net_warn_ratelimited("Packet exceeded mirred recursion limit on dev %s\n",
31973db191dSVictor Nogueira 				     netdev_name(skb->dev));
32073db191dSVictor Nogueira 		retval = TC_ACT_SHOT;
32173db191dSVictor Nogueira 		goto dec_nest_level;
32273db191dSVictor Nogueira 	}
32373db191dSVictor Nogueira 
32473db191dSVictor Nogueira 	tcf_lastuse_update(&m->tcf_tm);
32573db191dSVictor Nogueira 	tcf_action_update_bstats(&m->common, skb);
32673db191dSVictor Nogueira 
32773db191dSVictor Nogueira 	dev = rcu_dereference_bh(m->tcfm_dev);
32873db191dSVictor Nogueira 	if (unlikely(!dev)) {
32973db191dSVictor Nogueira 		pr_notice_once("tc mirred: target device is gone\n");
33073db191dSVictor Nogueira 		tcf_action_inc_overlimit_qstats(&m->common);
33173db191dSVictor Nogueira 		goto dec_nest_level;
33273db191dSVictor Nogueira 	}
33373db191dSVictor Nogueira 
33473db191dSVictor Nogueira 	m_mac_header_xmit = READ_ONCE(m->tcfm_mac_header_xmit);
33573db191dSVictor Nogueira 	m_eaction = READ_ONCE(m->tcfm_eaction);
33673db191dSVictor Nogueira 
33773db191dSVictor Nogueira 	retval = tcf_mirred_to_dev(skb, m, dev, m_mac_header_xmit, m_eaction,
33873db191dSVictor Nogueira 				   retval);
33973db191dSVictor Nogueira 
34073db191dSVictor Nogueira dec_nest_level:
34178dcdffeSDavide Caratti 	__this_cpu_dec(mirred_nest_level);
342feed1f17SChangli Gao 
343feed1f17SChangli Gao 	return retval;
3444bba3925SPatrick McHardy }
3454bba3925SPatrick McHardy 
tcf_stats_update(struct tc_action * a,u64 bytes,u64 packets,u64 drops,u64 lastuse,bool hw)3464b61d3e8SPo Liu static void tcf_stats_update(struct tc_action *a, u64 bytes, u64 packets,
3474b61d3e8SPo Liu 			     u64 drops, u64 lastuse, bool hw)
3489798e6feSJakub Kicinski {
3495712bf9cSPaul Blakey 	struct tcf_mirred *m = to_mirred(a);
3505712bf9cSPaul Blakey 	struct tcf_t *tm = &m->tcf_tm;
3515712bf9cSPaul Blakey 
3524b61d3e8SPo Liu 	tcf_action_update_stats(a, bytes, packets, drops, hw);
3533bb23421SRoi Dayan 	tm->lastuse = max_t(u64, tm->lastuse, lastuse);
3549798e6feSJakub Kicinski }
3559798e6feSJakub Kicinski 
tcf_mirred_dump(struct sk_buff * skb,struct tc_action * a,int bind,int ref)3565a7a5555SJamal Hadi Salim static int tcf_mirred_dump(struct sk_buff *skb, struct tc_action *a, int bind,
3575a7a5555SJamal Hadi Salim 			   int ref)
3584bba3925SPatrick McHardy {
35927a884dcSArnaldo Carvalho de Melo 	unsigned char *b = skb_tail_pointer(skb);
360a85a970aSWANG Cong 	struct tcf_mirred *m = to_mirred(a);
3611c40be12SEric Dumazet 	struct tc_mirred opt = {
3621c40be12SEric Dumazet 		.index   = m->tcf_index,
363036bb443SVlad Buslov 		.refcnt  = refcount_read(&m->tcf_refcnt) - ref,
364036bb443SVlad Buslov 		.bindcnt = atomic_read(&m->tcf_bindcnt) - bind,
3651c40be12SEric Dumazet 	};
3664e232818SVlad Buslov 	struct net_device *dev;
3674bba3925SPatrick McHardy 	struct tcf_t t;
3684bba3925SPatrick McHardy 
369653cd284SVlad Buslov 	spin_lock_bh(&m->tcf_lock);
3704e232818SVlad Buslov 	opt.action = m->tcf_action;
3714e232818SVlad Buslov 	opt.eaction = m->tcfm_eaction;
3724e232818SVlad Buslov 	dev = tcf_mirred_dev_dereference(m);
3734e232818SVlad Buslov 	if (dev)
3744e232818SVlad Buslov 		opt.ifindex = dev->ifindex;
3754e232818SVlad Buslov 
3761b34ec43SDavid S. Miller 	if (nla_put(skb, TCA_MIRRED_PARMS, sizeof(opt), &opt))
3771b34ec43SDavid S. Miller 		goto nla_put_failure;
37848d8ee16SJamal Hadi Salim 
37948d8ee16SJamal Hadi Salim 	tcf_tm_dump(&t, &m->tcf_tm);
3809854518eSNicolas Dichtel 	if (nla_put_64bit(skb, TCA_MIRRED_TM, sizeof(t), &t, TCA_MIRRED_PAD))
3811b34ec43SDavid S. Miller 		goto nla_put_failure;
382653cd284SVlad Buslov 	spin_unlock_bh(&m->tcf_lock);
3834e232818SVlad Buslov 
3844bba3925SPatrick McHardy 	return skb->len;
3854bba3925SPatrick McHardy 
3867ba699c6SPatrick McHardy nla_put_failure:
387653cd284SVlad Buslov 	spin_unlock_bh(&m->tcf_lock);
388dc5fc579SArnaldo Carvalho de Melo 	nlmsg_trim(skb, b);
3894bba3925SPatrick McHardy 	return -1;
3904bba3925SPatrick McHardy }
3914bba3925SPatrick McHardy 
mirred_device_event(struct notifier_block * unused,unsigned long event,void * ptr)3923b87956eSstephen hemminger static int mirred_device_event(struct notifier_block *unused,
3933b87956eSstephen hemminger 			       unsigned long event, void *ptr)
3943b87956eSstephen hemminger {
395351638e7SJiri Pirko 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
3963b87956eSstephen hemminger 	struct tcf_mirred *m;
3973b87956eSstephen hemminger 
3982ee22a90SEric Dumazet 	ASSERT_RTNL();
3996bd00b85SWANG Cong 	if (event == NETDEV_UNREGISTER) {
4004e232818SVlad Buslov 		spin_lock(&mirred_list_lock);
4013b87956eSstephen hemminger 		list_for_each_entry(m, &mirred_list, tcfm_list) {
402653cd284SVlad Buslov 			spin_lock_bh(&m->tcf_lock);
4034e232818SVlad Buslov 			if (tcf_mirred_dev_dereference(m) == dev) {
404d62607c3SJakub Kicinski 				netdev_put(dev, &m->tcfm_dev_tracker);
4052ee22a90SEric Dumazet 				/* Note : no rcu grace period necessary, as
4062ee22a90SEric Dumazet 				 * net_device are already rcu protected.
4072ee22a90SEric Dumazet 				 */
4082ee22a90SEric Dumazet 				RCU_INIT_POINTER(m->tcfm_dev, NULL);
4093b87956eSstephen hemminger 			}
410653cd284SVlad Buslov 			spin_unlock_bh(&m->tcf_lock);
4113b87956eSstephen hemminger 		}
4124e232818SVlad Buslov 		spin_unlock(&mirred_list_lock);
4136bd00b85SWANG Cong 	}
4143b87956eSstephen hemminger 
4153b87956eSstephen hemminger 	return NOTIFY_DONE;
4163b87956eSstephen hemminger }
4173b87956eSstephen hemminger 
4183b87956eSstephen hemminger static struct notifier_block mirred_device_notifier = {
4193b87956eSstephen hemminger 	.notifier_call = mirred_device_event,
4203b87956eSstephen hemminger };
4213b87956eSstephen hemminger 
tcf_mirred_dev_put(void * priv)422470d5060SVlad Buslov static void tcf_mirred_dev_put(void *priv)
423470d5060SVlad Buslov {
424470d5060SVlad Buslov 	struct net_device *dev = priv;
425470d5060SVlad Buslov 
426470d5060SVlad Buslov 	dev_put(dev);
427470d5060SVlad Buslov }
428470d5060SVlad Buslov 
429470d5060SVlad Buslov static struct net_device *
tcf_mirred_get_dev(const struct tc_action * a,tc_action_priv_destructor * destructor)430470d5060SVlad Buslov tcf_mirred_get_dev(const struct tc_action *a,
431470d5060SVlad Buslov 		   tc_action_priv_destructor *destructor)
432255cb304SHadar Hen Zion {
433843e79d0SJiri Pirko 	struct tcf_mirred *m = to_mirred(a);
4344e232818SVlad Buslov 	struct net_device *dev;
435255cb304SHadar Hen Zion 
4364e232818SVlad Buslov 	rcu_read_lock();
4374e232818SVlad Buslov 	dev = rcu_dereference(m->tcfm_dev);
438470d5060SVlad Buslov 	if (dev) {
43984a75b32SVlad Buslov 		dev_hold(dev);
440470d5060SVlad Buslov 		*destructor = tcf_mirred_dev_put;
441470d5060SVlad Buslov 	}
4424e232818SVlad Buslov 	rcu_read_unlock();
44384a75b32SVlad Buslov 
44484a75b32SVlad Buslov 	return dev;
44584a75b32SVlad Buslov }
44684a75b32SVlad Buslov 
tcf_mirred_get_fill_size(const struct tc_action * act)447b84b2d4eSRoman Mashak static size_t tcf_mirred_get_fill_size(const struct tc_action *act)
448b84b2d4eSRoman Mashak {
449b84b2d4eSRoman Mashak 	return nla_total_size(sizeof(struct tc_mirred));
450b84b2d4eSRoman Mashak }
451b84b2d4eSRoman Mashak 
tcf_offload_mirred_get_dev(struct flow_action_entry * entry,const struct tc_action * act)452c54e1d92SBaowen Zheng static void tcf_offload_mirred_get_dev(struct flow_action_entry *entry,
453c54e1d92SBaowen Zheng 				       const struct tc_action *act)
454c54e1d92SBaowen Zheng {
455c54e1d92SBaowen Zheng 	entry->dev = act->ops->get_dev(act, &entry->destructor);
456c54e1d92SBaowen Zheng 	if (!entry->dev)
457c54e1d92SBaowen Zheng 		return;
458c54e1d92SBaowen Zheng 	entry->destructor_priv = entry->dev;
459c54e1d92SBaowen Zheng }
460c54e1d92SBaowen Zheng 
tcf_mirred_offload_act_setup(struct tc_action * act,void * entry_data,u32 * index_inc,bool bind,struct netlink_ext_ack * extack)461c54e1d92SBaowen Zheng static int tcf_mirred_offload_act_setup(struct tc_action *act, void *entry_data,
462c2ccf84eSIdo Schimmel 					u32 *index_inc, bool bind,
463c2ccf84eSIdo Schimmel 					struct netlink_ext_ack *extack)
464c54e1d92SBaowen Zheng {
465c54e1d92SBaowen Zheng 	if (bind) {
466c54e1d92SBaowen Zheng 		struct flow_action_entry *entry = entry_data;
467c54e1d92SBaowen Zheng 
468c54e1d92SBaowen Zheng 		if (is_tcf_mirred_egress_redirect(act)) {
469c54e1d92SBaowen Zheng 			entry->id = FLOW_ACTION_REDIRECT;
470c54e1d92SBaowen Zheng 			tcf_offload_mirred_get_dev(entry, act);
471c54e1d92SBaowen Zheng 		} else if (is_tcf_mirred_egress_mirror(act)) {
472c54e1d92SBaowen Zheng 			entry->id = FLOW_ACTION_MIRRED;
473c54e1d92SBaowen Zheng 			tcf_offload_mirred_get_dev(entry, act);
474c54e1d92SBaowen Zheng 		} else if (is_tcf_mirred_ingress_redirect(act)) {
475c54e1d92SBaowen Zheng 			entry->id = FLOW_ACTION_REDIRECT_INGRESS;
476c54e1d92SBaowen Zheng 			tcf_offload_mirred_get_dev(entry, act);
477c54e1d92SBaowen Zheng 		} else if (is_tcf_mirred_ingress_mirror(act)) {
478c54e1d92SBaowen Zheng 			entry->id = FLOW_ACTION_MIRRED_INGRESS;
479c54e1d92SBaowen Zheng 			tcf_offload_mirred_get_dev(entry, act);
480c54e1d92SBaowen Zheng 		} else {
4814dcaa50dSIdo Schimmel 			NL_SET_ERR_MSG_MOD(extack, "Unsupported mirred offload");
482c54e1d92SBaowen Zheng 			return -EOPNOTSUPP;
483c54e1d92SBaowen Zheng 		}
484c54e1d92SBaowen Zheng 		*index_inc = 1;
485c54e1d92SBaowen Zheng 	} else {
4868cbfe939SBaowen Zheng 		struct flow_offload_action *fl_action = entry_data;
4878cbfe939SBaowen Zheng 
4888cbfe939SBaowen Zheng 		if (is_tcf_mirred_egress_redirect(act))
4898cbfe939SBaowen Zheng 			fl_action->id = FLOW_ACTION_REDIRECT;
4908cbfe939SBaowen Zheng 		else if (is_tcf_mirred_egress_mirror(act))
4918cbfe939SBaowen Zheng 			fl_action->id = FLOW_ACTION_MIRRED;
4928cbfe939SBaowen Zheng 		else if (is_tcf_mirred_ingress_redirect(act))
4938cbfe939SBaowen Zheng 			fl_action->id = FLOW_ACTION_REDIRECT_INGRESS;
4948cbfe939SBaowen Zheng 		else if (is_tcf_mirred_ingress_mirror(act))
4958cbfe939SBaowen Zheng 			fl_action->id = FLOW_ACTION_MIRRED_INGRESS;
4968cbfe939SBaowen Zheng 		else
497c54e1d92SBaowen Zheng 			return -EOPNOTSUPP;
498c54e1d92SBaowen Zheng 	}
499c54e1d92SBaowen Zheng 
500c54e1d92SBaowen Zheng 	return 0;
501c54e1d92SBaowen Zheng }
502c54e1d92SBaowen Zheng 
5034bba3925SPatrick McHardy static struct tc_action_ops act_mirred_ops = {
5044bba3925SPatrick McHardy 	.kind		=	"mirred",
505eddd2cf1SEli Cohen 	.id		=	TCA_ID_MIRRED,
5064bba3925SPatrick McHardy 	.owner		=	THIS_MODULE,
5077c5790c4SJamal Hadi Salim 	.act		=	tcf_mirred_act,
5089798e6feSJakub Kicinski 	.stats_update	=	tcf_stats_update,
5094bba3925SPatrick McHardy 	.dump		=	tcf_mirred_dump,
51086062033SWANG Cong 	.cleanup	=	tcf_mirred_release,
5114bba3925SPatrick McHardy 	.init		=	tcf_mirred_init,
512b84b2d4eSRoman Mashak 	.get_fill_size	=	tcf_mirred_get_fill_size,
513c54e1d92SBaowen Zheng 	.offload_act_setup =	tcf_mirred_offload_act_setup,
514a85a970aSWANG Cong 	.size		=	sizeof(struct tcf_mirred),
515843e79d0SJiri Pirko 	.get_dev	=	tcf_mirred_get_dev,
516ddf97ccdSWANG Cong };
517ddf97ccdSWANG Cong 
mirred_init_net(struct net * net)518ddf97ccdSWANG Cong static __net_init int mirred_init_net(struct net *net)
519ddf97ccdSWANG Cong {
520acd0a7abSZhengchao Shao 	struct tc_action_net *tn = net_generic(net, act_mirred_ops.net_id);
521ddf97ccdSWANG Cong 
522981471bdSCong Wang 	return tc_action_net_init(net, tn, &act_mirred_ops);
523ddf97ccdSWANG Cong }
524ddf97ccdSWANG Cong 
mirred_exit_net(struct list_head * net_list)525039af9c6SCong Wang static void __net_exit mirred_exit_net(struct list_head *net_list)
526ddf97ccdSWANG Cong {
527acd0a7abSZhengchao Shao 	tc_action_net_exit(net_list, act_mirred_ops.net_id);
528ddf97ccdSWANG Cong }
529ddf97ccdSWANG Cong 
530ddf97ccdSWANG Cong static struct pernet_operations mirred_net_ops = {
531ddf97ccdSWANG Cong 	.init = mirred_init_net,
532039af9c6SCong Wang 	.exit_batch = mirred_exit_net,
533acd0a7abSZhengchao Shao 	.id   = &act_mirred_ops.net_id,
534ddf97ccdSWANG Cong 	.size = sizeof(struct tc_action_net),
5354bba3925SPatrick McHardy };
5364bba3925SPatrick McHardy 
5374bba3925SPatrick McHardy MODULE_AUTHOR("Jamal Hadi Salim(2002)");
5384bba3925SPatrick McHardy MODULE_DESCRIPTION("Device Mirror/redirect actions");
5394bba3925SPatrick McHardy MODULE_LICENSE("GPL");
5404bba3925SPatrick McHardy 
mirred_init_module(void)541e9ce1cd3SDavid S. Miller static int __init mirred_init_module(void)
5424bba3925SPatrick McHardy {
5433b87956eSstephen hemminger 	int err = register_netdevice_notifier(&mirred_device_notifier);
5443b87956eSstephen hemminger 	if (err)
5453b87956eSstephen hemminger 		return err;
5463b87956eSstephen hemminger 
5476ff9c364Sstephen hemminger 	pr_info("Mirror/redirect action on\n");
54811c9a7d3SYueHaibing 	err = tcf_register_action(&act_mirred_ops, &mirred_net_ops);
54911c9a7d3SYueHaibing 	if (err)
55011c9a7d3SYueHaibing 		unregister_netdevice_notifier(&mirred_device_notifier);
55111c9a7d3SYueHaibing 
55211c9a7d3SYueHaibing 	return err;
5534bba3925SPatrick McHardy }
5544bba3925SPatrick McHardy 
mirred_cleanup_module(void)555e9ce1cd3SDavid S. Miller static void __exit mirred_cleanup_module(void)
5564bba3925SPatrick McHardy {
557ddf97ccdSWANG Cong 	tcf_unregister_action(&act_mirred_ops, &mirred_net_ops);
558568a153aSWANG Cong 	unregister_netdevice_notifier(&mirred_device_notifier);
5594bba3925SPatrick McHardy }
5604bba3925SPatrick McHardy 
5614bba3925SPatrick McHardy module_init(mirred_init_module);
5624bba3925SPatrick McHardy module_exit(mirred_cleanup_module);
563