Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 | 2 | /* |
0c6965dd | 3 | * net/sched/act_mirred.c packet mirroring and redirect actions |
1da177e4 | 4 | * |
1da177e4 LT |
5 | * Authors: Jamal Hadi Salim (2002-4) |
6 | * | |
7 | * TODO: Add ingress support (and socket redirect support) | |
1da177e4 LT |
8 | */ |
9 | ||
1da177e4 LT |
10 | #include <linux/types.h> |
11 | #include <linux/kernel.h> | |
1da177e4 | 12 | #include <linux/string.h> |
1da177e4 | 13 | #include <linux/errno.h> |
1da177e4 LT |
14 | #include <linux/skbuff.h> |
15 | #include <linux/rtnetlink.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/init.h> | |
5a0e3ad6 | 18 | #include <linux/gfp.h> |
c491680f | 19 | #include <linux/if_arp.h> |
881d966b | 20 | #include <net/net_namespace.h> |
dc5fc579 | 21 | #include <net/netlink.h> |
f799ada6 | 22 | #include <net/dst.h> |
1da177e4 | 23 | #include <net/pkt_sched.h> |
e5cf1baf | 24 | #include <net/pkt_cls.h> |
1da177e4 LT |
25 | #include <linux/tc_act/tc_mirred.h> |
26 | #include <net/tc_act/tc_mirred.h> | |
871cf386 | 27 | #include <net/tc_wrapper.h> |
1da177e4 | 28 | |
3b87956e | 29 | static LIST_HEAD(mirred_list); |
4e232818 | 30 | static DEFINE_SPINLOCK(mirred_list_lock); |
1da177e4 | 31 | |
78dcdffe DC |
32 | #define MIRRED_NEST_LIMIT 4 |
33 | static DEFINE_PER_CPU(unsigned int, mirred_nest_level); | |
e2ca070f | 34 | |
53592b36 SL |
35 | static bool tcf_mirred_is_act_redirect(int action) |
36 | { | |
37 | return action == TCA_EGRESS_REDIR || action == TCA_INGRESS_REDIR; | |
38 | } | |
39 | ||
8dc07fdb | 40 | static bool tcf_mirred_act_wants_ingress(int action) |
53592b36 SL |
41 | { |
42 | switch (action) { | |
43 | case TCA_EGRESS_REDIR: | |
44 | case TCA_EGRESS_MIRROR: | |
8dc07fdb | 45 | return false; |
53592b36 SL |
46 | case TCA_INGRESS_REDIR: |
47 | case TCA_INGRESS_MIRROR: | |
8dc07fdb | 48 | return true; |
53592b36 SL |
49 | default: |
50 | BUG(); | |
51 | } | |
52 | } | |
53 | ||
e5cf1baf PA |
54 | static bool tcf_mirred_can_reinsert(int action) |
55 | { | |
56 | switch (action) { | |
57 | case TC_ACT_SHOT: | |
58 | case TC_ACT_STOLEN: | |
59 | case TC_ACT_QUEUED: | |
60 | case TC_ACT_TRAP: | |
61 | return true; | |
62 | } | |
63 | return false; | |
64 | } | |
65 | ||
4e232818 VB |
66 | static struct net_device *tcf_mirred_dev_dereference(struct tcf_mirred *m) |
67 | { | |
68 | return rcu_dereference_protected(m->tcfm_dev, | |
69 | lockdep_is_held(&m->tcf_lock)); | |
70 | } | |
71 | ||
9a63b255 | 72 | static void tcf_mirred_release(struct tc_action *a) |
1da177e4 | 73 | { |
86062033 | 74 | struct tcf_mirred *m = to_mirred(a); |
dc327f89 | 75 | struct net_device *dev; |
2ee22a90 | 76 | |
4e232818 | 77 | spin_lock(&mirred_list_lock); |
a5b5c958 | 78 | list_del(&m->tcfm_list); |
4e232818 VB |
79 | spin_unlock(&mirred_list_lock); |
80 | ||
81 | /* last reference to action, no need to lock */ | |
82 | dev = rcu_dereference_protected(m->tcfm_dev, 1); | |
d62607c3 | 83 | netdev_put(dev, &m->tcfm_dev_tracker); |
1da177e4 LT |
84 | } |
85 | ||
53b2bf3f PM |
86 | static const struct nla_policy mirred_policy[TCA_MIRRED_MAX + 1] = { |
87 | [TCA_MIRRED_PARMS] = { .len = sizeof(struct tc_mirred) }, | |
88 | }; | |
89 | ||
a85a970a | 90 | static struct tc_action_ops act_mirred_ops; |
ddf97ccd | 91 | |
c1b52739 | 92 | static int tcf_mirred_init(struct net *net, struct nlattr *nla, |
789871bb | 93 | struct nlattr *est, struct tc_action **a, |
85d0966f | 94 | struct tcf_proto *tp, |
abbb0d33 | 95 | u32 flags, struct netlink_ext_ack *extack) |
1da177e4 | 96 | { |
acd0a7ab | 97 | struct tc_action_net *tn = net_generic(net, act_mirred_ops.net_id); |
695176bf | 98 | bool bind = flags & TCA_ACT_FLAGS_BIND; |
7ba699c6 | 99 | struct nlattr *tb[TCA_MIRRED_MAX + 1]; |
ff9721d3 | 100 | struct tcf_chain *goto_ch = NULL; |
16577923 | 101 | bool mac_header_xmit = false; |
1da177e4 | 102 | struct tc_mirred *parm; |
e9ce1cd3 | 103 | struct tcf_mirred *m; |
b2313077 | 104 | bool exists = false; |
0190c1d4 | 105 | int ret, err; |
7be8ef2c | 106 | u32 index; |
1da177e4 | 107 | |
1d4760c7 AA |
108 | if (!nla) { |
109 | NL_SET_ERR_MSG_MOD(extack, "Mirred requires attributes to be passed"); | |
1da177e4 | 110 | return -EINVAL; |
1d4760c7 | 111 | } |
8cb08174 JB |
112 | ret = nla_parse_nested_deprecated(tb, TCA_MIRRED_MAX, nla, |
113 | mirred_policy, extack); | |
b76965e0 CG |
114 | if (ret < 0) |
115 | return ret; | |
1d4760c7 AA |
116 | if (!tb[TCA_MIRRED_PARMS]) { |
117 | NL_SET_ERR_MSG_MOD(extack, "Missing required mirred parameters"); | |
1da177e4 | 118 | return -EINVAL; |
1d4760c7 | 119 | } |
7ba699c6 | 120 | parm = nla_data(tb[TCA_MIRRED_PARMS]); |
7be8ef2c DL |
121 | index = parm->index; |
122 | err = tcf_idr_check_alloc(tn, &index, a, bind); | |
0190c1d4 VB |
123 | if (err < 0) |
124 | return err; | |
125 | exists = err; | |
87dfbdc6 JHS |
126 | if (exists && bind) |
127 | return 0; | |
128 | ||
b76965e0 CG |
129 | switch (parm->eaction) { |
130 | case TCA_EGRESS_MIRROR: | |
131 | case TCA_EGRESS_REDIR: | |
53592b36 SL |
132 | case TCA_INGRESS_REDIR: |
133 | case TCA_INGRESS_MIRROR: | |
b76965e0 CG |
134 | break; |
135 | default: | |
87dfbdc6 | 136 | if (exists) |
65a206c0 | 137 | tcf_idr_release(*a, bind); |
0190c1d4 | 138 | else |
7be8ef2c | 139 | tcf_idr_cleanup(tn, index); |
1d4760c7 | 140 | NL_SET_ERR_MSG_MOD(extack, "Unknown mirred option"); |
b76965e0 CG |
141 | return -EINVAL; |
142 | } | |
1da177e4 | 143 | |
87dfbdc6 | 144 | if (!exists) { |
4e232818 | 145 | if (!parm->ifindex) { |
7be8ef2c | 146 | tcf_idr_cleanup(tn, index); |
1d4760c7 | 147 | NL_SET_ERR_MSG_MOD(extack, "Specified device does not exist"); |
1da177e4 | 148 | return -EINVAL; |
1d4760c7 | 149 | } |
e3822678 VB |
150 | ret = tcf_idr_create_from_flags(tn, index, est, a, |
151 | &act_mirred_ops, bind, flags); | |
0190c1d4 | 152 | if (ret) { |
7be8ef2c | 153 | tcf_idr_cleanup(tn, index); |
86062033 | 154 | return ret; |
0190c1d4 | 155 | } |
1da177e4 | 156 | ret = ACT_P_CREATED; |
695176bf | 157 | } else if (!(flags & TCA_ACT_FLAGS_REPLACE)) { |
65a206c0 | 158 | tcf_idr_release(*a, bind); |
4e8ddd7f | 159 | return -EEXIST; |
1da177e4 | 160 | } |
064c5d68 JH |
161 | |
162 | m = to_mirred(*a); | |
163 | if (ret == ACT_P_CREATED) | |
164 | INIT_LIST_HEAD(&m->tcfm_list); | |
165 | ||
ff9721d3 DC |
166 | err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); |
167 | if (err < 0) | |
168 | goto release_idr; | |
169 | ||
653cd284 | 170 | spin_lock_bh(&m->tcf_lock); |
4e232818 VB |
171 | |
172 | if (parm->ifindex) { | |
ada066b2 ED |
173 | struct net_device *odev, *ndev; |
174 | ||
175 | ndev = dev_get_by_index(net, parm->ifindex); | |
176 | if (!ndev) { | |
653cd284 | 177 | spin_unlock_bh(&m->tcf_lock); |
ff9721d3 DC |
178 | err = -ENODEV; |
179 | goto put_chain; | |
4e232818 | 180 | } |
ada066b2 ED |
181 | mac_header_xmit = dev_is_mac_header_xmit(ndev); |
182 | odev = rcu_replace_pointer(m->tcfm_dev, ndev, | |
445d3749 | 183 | lockdep_is_held(&m->tcf_lock)); |
d62607c3 | 184 | netdev_put(odev, &m->tcfm_dev_tracker); |
ada066b2 | 185 | netdev_tracker_alloc(ndev, &m->tcfm_dev_tracker, GFP_ATOMIC); |
16577923 | 186 | m->tcfm_mac_header_xmit = mac_header_xmit; |
1da177e4 | 187 | } |
ff9721d3 DC |
188 | goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch); |
189 | m->tcfm_eaction = parm->eaction; | |
653cd284 | 190 | spin_unlock_bh(&m->tcf_lock); |
ff9721d3 DC |
191 | if (goto_ch) |
192 | tcf_chain_put_by_act(goto_ch); | |
2ee22a90 | 193 | |
3b87956e | 194 | if (ret == ACT_P_CREATED) { |
4e232818 | 195 | spin_lock(&mirred_list_lock); |
3b87956e | 196 | list_add(&m->tcfm_list, &mirred_list); |
4e232818 | 197 | spin_unlock(&mirred_list_lock); |
3b87956e | 198 | } |
1da177e4 | 199 | |
1da177e4 | 200 | return ret; |
ff9721d3 DC |
201 | put_chain: |
202 | if (goto_ch) | |
203 | tcf_chain_put_by_act(goto_ch); | |
204 | release_idr: | |
205 | tcf_idr_release(*a, bind); | |
206 | return err; | |
1da177e4 LT |
207 | } |
208 | ||
ca22da2f DC |
209 | static bool is_mirred_nested(void) |
210 | { | |
211 | return unlikely(__this_cpu_read(mirred_nest_level) > 1); | |
212 | } | |
213 | ||
fa6d6399 | 214 | static int tcf_mirred_forward(bool want_ingress, struct sk_buff *skb) |
215 | { | |
216 | int err; | |
217 | ||
218 | if (!want_ingress) | |
c129412f | 219 | err = tcf_dev_queue_xmit(skb, dev_queue_xmit); |
ca22da2f DC |
220 | else if (is_mirred_nested()) |
221 | err = netif_rx(skb); | |
fa6d6399 | 222 | else |
223 | err = netif_receive_skb(skb); | |
224 | ||
225 | return err; | |
226 | } | |
227 | ||
871cf386 PT |
228 | TC_INDIRECT_SCOPE int tcf_mirred_act(struct sk_buff *skb, |
229 | const struct tc_action *a, | |
230 | struct tcf_result *res) | |
1da177e4 | 231 | { |
a85a970a | 232 | struct tcf_mirred *m = to_mirred(a); |
e5cf1baf | 233 | struct sk_buff *skb2 = skb; |
53592b36 | 234 | bool m_mac_header_xmit; |
1da177e4 | 235 | struct net_device *dev; |
78dcdffe | 236 | unsigned int nest_level; |
53592b36 | 237 | int retval, err = 0; |
e5cf1baf PA |
238 | bool use_reinsert; |
239 | bool want_ingress; | |
240 | bool is_redirect; | |
70cf3dc7 | 241 | bool expects_nh; |
f799ada6 | 242 | bool at_ingress; |
53592b36 SL |
243 | int m_eaction; |
244 | int mac_len; | |
70cf3dc7 | 245 | bool at_nh; |
1da177e4 | 246 | |
78dcdffe DC |
247 | nest_level = __this_cpu_inc_return(mirred_nest_level); |
248 | if (unlikely(nest_level > MIRRED_NEST_LIMIT)) { | |
e2ca070f JH |
249 | net_warn_ratelimited("Packet exceeded mirred recursion limit on dev %s\n", |
250 | netdev_name(skb->dev)); | |
78dcdffe | 251 | __this_cpu_dec(mirred_nest_level); |
e2ca070f JH |
252 | return TC_ACT_SHOT; |
253 | } | |
254 | ||
2ee22a90 | 255 | tcf_lastuse_update(&m->tcf_tm); |
5e1ad95b | 256 | tcf_action_update_bstats(&m->common, skb); |
1da177e4 | 257 | |
53592b36 SL |
258 | m_mac_header_xmit = READ_ONCE(m->tcfm_mac_header_xmit); |
259 | m_eaction = READ_ONCE(m->tcfm_eaction); | |
2ee22a90 | 260 | retval = READ_ONCE(m->tcf_action); |
7fd4b288 | 261 | dev = rcu_dereference_bh(m->tcfm_dev); |
2ee22a90 ED |
262 | if (unlikely(!dev)) { |
263 | pr_notice_once("tc mirred: target device is gone\n"); | |
3b87956e | 264 | goto out; |
265 | } | |
266 | ||
2ee22a90 | 267 | if (unlikely(!(dev->flags & IFF_UP))) { |
e87cc472 JP |
268 | net_notice_ratelimited("tc mirred to Houston: device %s is down\n", |
269 | dev->name); | |
feed1f17 | 270 | goto out; |
1da177e4 LT |
271 | } |
272 | ||
e5cf1baf PA |
273 | /* we could easily avoid the clone only if called by ingress and clsact; |
274 | * since we can't easily detect the clsact caller, skip clone only for | |
275 | * ingress - that covers the TC S/W datapath. | |
276 | */ | |
277 | is_redirect = tcf_mirred_is_act_redirect(m_eaction); | |
f799ada6 XL |
278 | at_ingress = skb_at_tc_ingress(skb); |
279 | use_reinsert = at_ingress && is_redirect && | |
e5cf1baf PA |
280 | tcf_mirred_can_reinsert(retval); |
281 | if (!use_reinsert) { | |
282 | skb2 = skb_clone(skb, GFP_ATOMIC); | |
283 | if (!skb2) | |
284 | goto out; | |
285 | } | |
1da177e4 | 286 | |
f799ada6 XL |
287 | want_ingress = tcf_mirred_act_wants_ingress(m_eaction); |
288 | ||
d09c548d HL |
289 | /* All mirred/redirected skbs should clear previous ct info */ |
290 | nf_reset_ct(skb2); | |
f799ada6 XL |
291 | if (want_ingress && !at_ingress) /* drop dst for egress -> ingress */ |
292 | skb_dst_drop(skb2); | |
70cf3dc7 SL |
293 | |
294 | expects_nh = want_ingress || !m_mac_header_xmit; | |
295 | at_nh = skb->data == skb_network_header(skb); | |
296 | if (at_nh != expects_nh) { | |
297 | mac_len = skb_at_tc_ingress(skb) ? skb->mac_len : | |
298 | skb_network_header(skb) - skb_mac_header(skb); | |
299 | if (expects_nh) { | |
300 | /* target device/action expect data at nh */ | |
53592b36 SL |
301 | skb_pull_rcsum(skb2, mac_len); |
302 | } else { | |
70cf3dc7 SL |
303 | /* target device/action expect data at mac */ |
304 | skb_push_rcsum(skb2, mac_len); | |
53592b36 | 305 | } |
feed1f17 | 306 | } |
1da177e4 | 307 | |
e5cf1baf PA |
308 | skb2->skb_iif = skb->dev->ifindex; |
309 | skb2->dev = dev; | |
310 | ||
1da177e4 | 311 | /* mirror is always swallowed */ |
e5cf1baf | 312 | if (is_redirect) { |
2c64605b PNA |
313 | skb_set_redirected(skb2, skb2->tc_at_ingress); |
314 | ||
e5cf1baf PA |
315 | /* let's the caller reinsert the packet, if possible */ |
316 | if (use_reinsert) { | |
1d14b30b | 317 | err = tcf_mirred_forward(want_ingress, skb); |
fa6d6399 | 318 | if (err) |
ef816f3c | 319 | tcf_action_inc_overlimit_qstats(&m->common); |
78dcdffe | 320 | __this_cpu_dec(mirred_nest_level); |
720f22fe | 321 | return TC_ACT_CONSUMED; |
e5cf1baf | 322 | } |
bc31c905 | 323 | } |
1da177e4 | 324 | |
fa6d6399 | 325 | err = tcf_mirred_forward(want_ingress, skb2); |
feed1f17 | 326 | if (err) { |
2ee22a90 | 327 | out: |
26b537a8 | 328 | tcf_action_inc_overlimit_qstats(&m->common); |
53592b36 | 329 | if (tcf_mirred_is_act_redirect(m_eaction)) |
16c0b164 | 330 | retval = TC_ACT_SHOT; |
2ee22a90 | 331 | } |
78dcdffe | 332 | __this_cpu_dec(mirred_nest_level); |
feed1f17 CG |
333 | |
334 | return retval; | |
1da177e4 LT |
335 | } |
336 | ||
4b61d3e8 PL |
337 | static void tcf_stats_update(struct tc_action *a, u64 bytes, u64 packets, |
338 | u64 drops, u64 lastuse, bool hw) | |
9798e6fe | 339 | { |
5712bf9c PB |
340 | struct tcf_mirred *m = to_mirred(a); |
341 | struct tcf_t *tm = &m->tcf_tm; | |
342 | ||
4b61d3e8 | 343 | tcf_action_update_stats(a, bytes, packets, drops, hw); |
3bb23421 | 344 | tm->lastuse = max_t(u64, tm->lastuse, lastuse); |
9798e6fe JK |
345 | } |
346 | ||
5a7a5555 JHS |
347 | static int tcf_mirred_dump(struct sk_buff *skb, struct tc_action *a, int bind, |
348 | int ref) | |
1da177e4 | 349 | { |
27a884dc | 350 | unsigned char *b = skb_tail_pointer(skb); |
a85a970a | 351 | struct tcf_mirred *m = to_mirred(a); |
1c40be12 ED |
352 | struct tc_mirred opt = { |
353 | .index = m->tcf_index, | |
036bb443 VB |
354 | .refcnt = refcount_read(&m->tcf_refcnt) - ref, |
355 | .bindcnt = atomic_read(&m->tcf_bindcnt) - bind, | |
1c40be12 | 356 | }; |
4e232818 | 357 | struct net_device *dev; |
1da177e4 LT |
358 | struct tcf_t t; |
359 | ||
653cd284 | 360 | spin_lock_bh(&m->tcf_lock); |
4e232818 VB |
361 | opt.action = m->tcf_action; |
362 | opt.eaction = m->tcfm_eaction; | |
363 | dev = tcf_mirred_dev_dereference(m); | |
364 | if (dev) | |
365 | opt.ifindex = dev->ifindex; | |
366 | ||
1b34ec43 DM |
367 | if (nla_put(skb, TCA_MIRRED_PARMS, sizeof(opt), &opt)) |
368 | goto nla_put_failure; | |
48d8ee16 JHS |
369 | |
370 | tcf_tm_dump(&t, &m->tcf_tm); | |
9854518e | 371 | if (nla_put_64bit(skb, TCA_MIRRED_TM, sizeof(t), &t, TCA_MIRRED_PAD)) |
1b34ec43 | 372 | goto nla_put_failure; |
653cd284 | 373 | spin_unlock_bh(&m->tcf_lock); |
4e232818 | 374 | |
1da177e4 LT |
375 | return skb->len; |
376 | ||
7ba699c6 | 377 | nla_put_failure: |
653cd284 | 378 | spin_unlock_bh(&m->tcf_lock); |
dc5fc579 | 379 | nlmsg_trim(skb, b); |
1da177e4 LT |
380 | return -1; |
381 | } | |
382 | ||
3b87956e | 383 | static int mirred_device_event(struct notifier_block *unused, |
384 | unsigned long event, void *ptr) | |
385 | { | |
351638e7 | 386 | struct net_device *dev = netdev_notifier_info_to_dev(ptr); |
3b87956e | 387 | struct tcf_mirred *m; |
388 | ||
2ee22a90 | 389 | ASSERT_RTNL(); |
6bd00b85 | 390 | if (event == NETDEV_UNREGISTER) { |
4e232818 | 391 | spin_lock(&mirred_list_lock); |
3b87956e | 392 | list_for_each_entry(m, &mirred_list, tcfm_list) { |
653cd284 | 393 | spin_lock_bh(&m->tcf_lock); |
4e232818 | 394 | if (tcf_mirred_dev_dereference(m) == dev) { |
d62607c3 | 395 | netdev_put(dev, &m->tcfm_dev_tracker); |
2ee22a90 ED |
396 | /* Note : no rcu grace period necessary, as |
397 | * net_device are already rcu protected. | |
398 | */ | |
399 | RCU_INIT_POINTER(m->tcfm_dev, NULL); | |
3b87956e | 400 | } |
653cd284 | 401 | spin_unlock_bh(&m->tcf_lock); |
3b87956e | 402 | } |
4e232818 | 403 | spin_unlock(&mirred_list_lock); |
6bd00b85 | 404 | } |
3b87956e | 405 | |
406 | return NOTIFY_DONE; | |
407 | } | |
408 | ||
409 | static struct notifier_block mirred_device_notifier = { | |
410 | .notifier_call = mirred_device_event, | |
411 | }; | |
412 | ||
470d5060 VB |
413 | static void tcf_mirred_dev_put(void *priv) |
414 | { | |
415 | struct net_device *dev = priv; | |
416 | ||
417 | dev_put(dev); | |
418 | } | |
419 | ||
420 | static struct net_device * | |
421 | tcf_mirred_get_dev(const struct tc_action *a, | |
422 | tc_action_priv_destructor *destructor) | |
255cb304 | 423 | { |
843e79d0 | 424 | struct tcf_mirred *m = to_mirred(a); |
4e232818 | 425 | struct net_device *dev; |
84a75b32 | 426 | |
4e232818 VB |
427 | rcu_read_lock(); |
428 | dev = rcu_dereference(m->tcfm_dev); | |
470d5060 | 429 | if (dev) { |
84a75b32 | 430 | dev_hold(dev); |
470d5060 VB |
431 | *destructor = tcf_mirred_dev_put; |
432 | } | |
4e232818 | 433 | rcu_read_unlock(); |
255cb304 | 434 | |
84a75b32 VB |
435 | return dev; |
436 | } | |
437 | ||
b84b2d4e RM |
438 | static size_t tcf_mirred_get_fill_size(const struct tc_action *act) |
439 | { | |
440 | return nla_total_size(sizeof(struct tc_mirred)); | |
441 | } | |
442 | ||
c54e1d92 BZ |
443 | static void tcf_offload_mirred_get_dev(struct flow_action_entry *entry, |
444 | const struct tc_action *act) | |
445 | { | |
446 | entry->dev = act->ops->get_dev(act, &entry->destructor); | |
447 | if (!entry->dev) | |
448 | return; | |
449 | entry->destructor_priv = entry->dev; | |
450 | } | |
451 | ||
452 | static int tcf_mirred_offload_act_setup(struct tc_action *act, void *entry_data, | |
c2ccf84e IS |
453 | u32 *index_inc, bool bind, |
454 | struct netlink_ext_ack *extack) | |
c54e1d92 BZ |
455 | { |
456 | if (bind) { | |
457 | struct flow_action_entry *entry = entry_data; | |
458 | ||
459 | if (is_tcf_mirred_egress_redirect(act)) { | |
460 | entry->id = FLOW_ACTION_REDIRECT; | |
461 | tcf_offload_mirred_get_dev(entry, act); | |
462 | } else if (is_tcf_mirred_egress_mirror(act)) { | |
463 | entry->id = FLOW_ACTION_MIRRED; | |
464 | tcf_offload_mirred_get_dev(entry, act); | |
465 | } else if (is_tcf_mirred_ingress_redirect(act)) { | |
466 | entry->id = FLOW_ACTION_REDIRECT_INGRESS; | |
467 | tcf_offload_mirred_get_dev(entry, act); | |
468 | } else if (is_tcf_mirred_ingress_mirror(act)) { | |
469 | entry->id = FLOW_ACTION_MIRRED_INGRESS; | |
470 | tcf_offload_mirred_get_dev(entry, act); | |
471 | } else { | |
4dcaa50d | 472 | NL_SET_ERR_MSG_MOD(extack, "Unsupported mirred offload"); |
c54e1d92 BZ |
473 | return -EOPNOTSUPP; |
474 | } | |
475 | *index_inc = 1; | |
476 | } else { | |
8cbfe939 BZ |
477 | struct flow_offload_action *fl_action = entry_data; |
478 | ||
479 | if (is_tcf_mirred_egress_redirect(act)) | |
480 | fl_action->id = FLOW_ACTION_REDIRECT; | |
481 | else if (is_tcf_mirred_egress_mirror(act)) | |
482 | fl_action->id = FLOW_ACTION_MIRRED; | |
483 | else if (is_tcf_mirred_ingress_redirect(act)) | |
484 | fl_action->id = FLOW_ACTION_REDIRECT_INGRESS; | |
485 | else if (is_tcf_mirred_ingress_mirror(act)) | |
486 | fl_action->id = FLOW_ACTION_MIRRED_INGRESS; | |
487 | else | |
488 | return -EOPNOTSUPP; | |
c54e1d92 BZ |
489 | } |
490 | ||
491 | return 0; | |
492 | } | |
493 | ||
1da177e4 LT |
494 | static struct tc_action_ops act_mirred_ops = { |
495 | .kind = "mirred", | |
eddd2cf1 | 496 | .id = TCA_ID_MIRRED, |
1da177e4 | 497 | .owner = THIS_MODULE, |
7c5790c4 | 498 | .act = tcf_mirred_act, |
9798e6fe | 499 | .stats_update = tcf_stats_update, |
1da177e4 | 500 | .dump = tcf_mirred_dump, |
86062033 | 501 | .cleanup = tcf_mirred_release, |
1da177e4 | 502 | .init = tcf_mirred_init, |
b84b2d4e | 503 | .get_fill_size = tcf_mirred_get_fill_size, |
c54e1d92 | 504 | .offload_act_setup = tcf_mirred_offload_act_setup, |
a85a970a | 505 | .size = sizeof(struct tcf_mirred), |
843e79d0 | 506 | .get_dev = tcf_mirred_get_dev, |
ddf97ccd WC |
507 | }; |
508 | ||
509 | static __net_init int mirred_init_net(struct net *net) | |
510 | { | |
acd0a7ab | 511 | struct tc_action_net *tn = net_generic(net, act_mirred_ops.net_id); |
ddf97ccd | 512 | |
981471bd | 513 | return tc_action_net_init(net, tn, &act_mirred_ops); |
ddf97ccd WC |
514 | } |
515 | ||
039af9c6 | 516 | static void __net_exit mirred_exit_net(struct list_head *net_list) |
ddf97ccd | 517 | { |
acd0a7ab | 518 | tc_action_net_exit(net_list, act_mirred_ops.net_id); |
ddf97ccd WC |
519 | } |
520 | ||
521 | static struct pernet_operations mirred_net_ops = { | |
522 | .init = mirred_init_net, | |
039af9c6 | 523 | .exit_batch = mirred_exit_net, |
acd0a7ab | 524 | .id = &act_mirred_ops.net_id, |
ddf97ccd | 525 | .size = sizeof(struct tc_action_net), |
1da177e4 LT |
526 | }; |
527 | ||
528 | MODULE_AUTHOR("Jamal Hadi Salim(2002)"); | |
529 | MODULE_DESCRIPTION("Device Mirror/redirect actions"); | |
530 | MODULE_LICENSE("GPL"); | |
531 | ||
e9ce1cd3 | 532 | static int __init mirred_init_module(void) |
1da177e4 | 533 | { |
3b87956e | 534 | int err = register_netdevice_notifier(&mirred_device_notifier); |
535 | if (err) | |
536 | return err; | |
537 | ||
6ff9c364 | 538 | pr_info("Mirror/redirect action on\n"); |
11c9a7d3 Y |
539 | err = tcf_register_action(&act_mirred_ops, &mirred_net_ops); |
540 | if (err) | |
541 | unregister_netdevice_notifier(&mirred_device_notifier); | |
542 | ||
543 | return err; | |
1da177e4 LT |
544 | } |
545 | ||
e9ce1cd3 | 546 | static void __exit mirred_cleanup_module(void) |
1da177e4 | 547 | { |
ddf97ccd | 548 | tcf_unregister_action(&act_mirred_ops, &mirred_net_ops); |
568a153a | 549 | unregister_netdevice_notifier(&mirred_device_notifier); |
1da177e4 LT |
550 | } |
551 | ||
552 | module_init(mirred_init_module); | |
553 | module_exit(mirred_cleanup_module); |