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