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