.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
---|
1 | 2 | /* |
---|
2 | 3 | * net/sched/act_mirred.c packet mirroring and redirect actions |
---|
3 | | - * |
---|
4 | | - * This program is free software; you can redistribute it and/or |
---|
5 | | - * modify it under the terms of the GNU General Public License |
---|
6 | | - * as published by the Free Software Foundation; either version |
---|
7 | | - * 2 of the License, or (at your option) any later version. |
---|
8 | 4 | * |
---|
9 | 5 | * Authors: Jamal Hadi Salim (2002-4) |
---|
10 | 6 | * |
---|
11 | 7 | * TODO: Add ingress support (and socket redirect support) |
---|
12 | | - * |
---|
13 | 8 | */ |
---|
14 | 9 | |
---|
15 | 10 | #include <linux/types.h> |
---|
.. | .. |
---|
24 | 19 | #include <linux/if_arp.h> |
---|
25 | 20 | #include <net/net_namespace.h> |
---|
26 | 21 | #include <net/netlink.h> |
---|
| 22 | +#include <net/dst.h> |
---|
27 | 23 | #include <net/pkt_sched.h> |
---|
28 | 24 | #include <net/pkt_cls.h> |
---|
29 | 25 | #include <linux/tc_act/tc_mirred.h> |
---|
.. | .. |
---|
31 | 27 | |
---|
32 | 28 | static LIST_HEAD(mirred_list); |
---|
33 | 29 | static DEFINE_SPINLOCK(mirred_list_lock); |
---|
| 30 | + |
---|
| 31 | +#define MIRRED_RECURSION_LIMIT 4 |
---|
| 32 | +static DEFINE_PER_CPU(unsigned int, mirred_rec_level); |
---|
34 | 33 | |
---|
35 | 34 | static bool tcf_mirred_is_act_redirect(int action) |
---|
36 | 35 | { |
---|
.. | .. |
---|
94 | 93 | static int tcf_mirred_init(struct net *net, struct nlattr *nla, |
---|
95 | 94 | struct nlattr *est, struct tc_action **a, |
---|
96 | 95 | int ovr, int bind, bool rtnl_held, |
---|
97 | | - struct netlink_ext_ack *extack) |
---|
| 96 | + struct tcf_proto *tp, |
---|
| 97 | + u32 flags, struct netlink_ext_ack *extack) |
---|
98 | 98 | { |
---|
99 | 99 | struct tc_action_net *tn = net_generic(net, mirred_net_id); |
---|
100 | 100 | struct nlattr *tb[TCA_MIRRED_MAX + 1]; |
---|
| 101 | + struct tcf_chain *goto_ch = NULL; |
---|
101 | 102 | bool mac_header_xmit = false; |
---|
102 | 103 | struct tc_mirred *parm; |
---|
103 | 104 | struct tcf_mirred *m; |
---|
.. | .. |
---|
110 | 111 | NL_SET_ERR_MSG_MOD(extack, "Mirred requires attributes to be passed"); |
---|
111 | 112 | return -EINVAL; |
---|
112 | 113 | } |
---|
113 | | - ret = nla_parse_nested(tb, TCA_MIRRED_MAX, nla, mirred_policy, extack); |
---|
| 114 | + ret = nla_parse_nested_deprecated(tb, TCA_MIRRED_MAX, nla, |
---|
| 115 | + mirred_policy, extack); |
---|
114 | 116 | if (ret < 0) |
---|
115 | 117 | return ret; |
---|
116 | 118 | if (!tb[TCA_MIRRED_PARMS]) { |
---|
.. | .. |
---|
147 | 149 | NL_SET_ERR_MSG_MOD(extack, "Specified device does not exist"); |
---|
148 | 150 | return -EINVAL; |
---|
149 | 151 | } |
---|
150 | | - ret = tcf_idr_create(tn, index, est, a, |
---|
151 | | - &act_mirred_ops, bind, true); |
---|
| 152 | + ret = tcf_idr_create_from_flags(tn, index, est, a, |
---|
| 153 | + &act_mirred_ops, bind, flags); |
---|
152 | 154 | if (ret) { |
---|
153 | 155 | tcf_idr_cleanup(tn, index); |
---|
154 | 156 | return ret; |
---|
.. | .. |
---|
158 | 160 | tcf_idr_release(*a, bind); |
---|
159 | 161 | return -EEXIST; |
---|
160 | 162 | } |
---|
161 | | - m = to_mirred(*a); |
---|
162 | 163 | |
---|
| 164 | + m = to_mirred(*a); |
---|
163 | 165 | if (ret == ACT_P_CREATED) |
---|
164 | 166 | INIT_LIST_HEAD(&m->tcfm_list); |
---|
165 | 167 | |
---|
| 168 | + err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); |
---|
| 169 | + if (err < 0) |
---|
| 170 | + goto release_idr; |
---|
| 171 | + |
---|
166 | 172 | spin_lock_bh(&m->tcf_lock); |
---|
167 | | - m->tcf_action = parm->action; |
---|
168 | | - m->tcfm_eaction = parm->eaction; |
---|
169 | 173 | |
---|
170 | 174 | if (parm->ifindex) { |
---|
171 | 175 | dev = dev_get_by_index(net, parm->ifindex); |
---|
172 | 176 | if (!dev) { |
---|
173 | 177 | spin_unlock_bh(&m->tcf_lock); |
---|
174 | | - tcf_idr_release(*a, bind); |
---|
175 | | - return -ENODEV; |
---|
| 178 | + err = -ENODEV; |
---|
| 179 | + goto put_chain; |
---|
176 | 180 | } |
---|
177 | 181 | mac_header_xmit = dev_is_mac_header_xmit(dev); |
---|
178 | | - rcu_swap_protected(m->tcfm_dev, dev, |
---|
179 | | - lockdep_is_held(&m->tcf_lock)); |
---|
| 182 | + dev = rcu_replace_pointer(m->tcfm_dev, dev, |
---|
| 183 | + lockdep_is_held(&m->tcf_lock)); |
---|
180 | 184 | if (dev) |
---|
181 | 185 | dev_put(dev); |
---|
182 | 186 | m->tcfm_mac_header_xmit = mac_header_xmit; |
---|
183 | 187 | } |
---|
| 188 | + goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch); |
---|
| 189 | + m->tcfm_eaction = parm->eaction; |
---|
184 | 190 | spin_unlock_bh(&m->tcf_lock); |
---|
| 191 | + if (goto_ch) |
---|
| 192 | + tcf_chain_put_by_act(goto_ch); |
---|
185 | 193 | |
---|
186 | 194 | if (ret == ACT_P_CREATED) { |
---|
187 | 195 | spin_lock(&mirred_list_lock); |
---|
188 | 196 | list_add(&m->tcfm_list, &mirred_list); |
---|
189 | 197 | spin_unlock(&mirred_list_lock); |
---|
190 | | - |
---|
191 | | - tcf_idr_insert(tn, *a); |
---|
192 | 198 | } |
---|
193 | 199 | |
---|
194 | 200 | return ret; |
---|
| 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; |
---|
195 | 207 | } |
---|
196 | 208 | |
---|
197 | 209 | static int tcf_mirred_act(struct sk_buff *skb, const struct tc_action *a, |
---|
.. | .. |
---|
201 | 213 | struct sk_buff *skb2 = skb; |
---|
202 | 214 | bool m_mac_header_xmit; |
---|
203 | 215 | struct net_device *dev; |
---|
| 216 | + unsigned int rec_level; |
---|
204 | 217 | int retval, err = 0; |
---|
205 | 218 | bool use_reinsert; |
---|
206 | 219 | bool want_ingress; |
---|
207 | 220 | bool is_redirect; |
---|
| 221 | + bool expects_nh; |
---|
| 222 | + bool at_ingress; |
---|
208 | 223 | int m_eaction; |
---|
209 | 224 | int mac_len; |
---|
| 225 | + bool at_nh; |
---|
| 226 | + |
---|
| 227 | + rec_level = __this_cpu_inc_return(mirred_rec_level); |
---|
| 228 | + if (unlikely(rec_level > MIRRED_RECURSION_LIMIT)) { |
---|
| 229 | + net_warn_ratelimited("Packet exceeded mirred recursion limit on dev %s\n", |
---|
| 230 | + netdev_name(skb->dev)); |
---|
| 231 | + __this_cpu_dec(mirred_rec_level); |
---|
| 232 | + return TC_ACT_SHOT; |
---|
| 233 | + } |
---|
210 | 234 | |
---|
211 | 235 | tcf_lastuse_update(&m->tcf_tm); |
---|
212 | | - bstats_cpu_update(this_cpu_ptr(m->common.cpu_bstats), skb); |
---|
| 236 | + tcf_action_update_bstats(&m->common, skb); |
---|
213 | 237 | |
---|
214 | 238 | m_mac_header_xmit = READ_ONCE(m->tcfm_mac_header_xmit); |
---|
215 | 239 | m_eaction = READ_ONCE(m->tcfm_eaction); |
---|
.. | .. |
---|
231 | 255 | * ingress - that covers the TC S/W datapath. |
---|
232 | 256 | */ |
---|
233 | 257 | is_redirect = tcf_mirred_is_act_redirect(m_eaction); |
---|
234 | | - use_reinsert = skb_at_tc_ingress(skb) && is_redirect && |
---|
| 258 | + at_ingress = skb_at_tc_ingress(skb); |
---|
| 259 | + use_reinsert = at_ingress && is_redirect && |
---|
235 | 260 | tcf_mirred_can_reinsert(retval); |
---|
236 | 261 | if (!use_reinsert) { |
---|
237 | 262 | skb2 = skb_clone(skb, GFP_ATOMIC); |
---|
.. | .. |
---|
239 | 264 | goto out; |
---|
240 | 265 | } |
---|
241 | 266 | |
---|
242 | | - /* If action's target direction differs than filter's direction, |
---|
243 | | - * and devices expect a mac header on xmit, then mac push/pull is |
---|
244 | | - * needed. |
---|
245 | | - */ |
---|
246 | 267 | want_ingress = tcf_mirred_act_wants_ingress(m_eaction); |
---|
247 | | - if (skb_at_tc_ingress(skb) != want_ingress && m_mac_header_xmit) { |
---|
248 | | - if (!skb_at_tc_ingress(skb)) { |
---|
249 | | - /* caught at egress, act ingress: pull mac */ |
---|
250 | | - mac_len = skb_network_header(skb) - skb_mac_header(skb); |
---|
| 268 | + |
---|
| 269 | + /* All mirred/redirected skbs should clear previous ct info */ |
---|
| 270 | + nf_reset_ct(skb2); |
---|
| 271 | + if (want_ingress && !at_ingress) /* drop dst for egress -> ingress */ |
---|
| 272 | + skb_dst_drop(skb2); |
---|
| 273 | + |
---|
| 274 | + expects_nh = want_ingress || !m_mac_header_xmit; |
---|
| 275 | + at_nh = skb->data == skb_network_header(skb); |
---|
| 276 | + if (at_nh != expects_nh) { |
---|
| 277 | + mac_len = skb_at_tc_ingress(skb) ? skb->mac_len : |
---|
| 278 | + skb_network_header(skb) - skb_mac_header(skb); |
---|
| 279 | + if (expects_nh) { |
---|
| 280 | + /* target device/action expect data at nh */ |
---|
251 | 281 | skb_pull_rcsum(skb2, mac_len); |
---|
252 | 282 | } else { |
---|
253 | | - /* caught at ingress, act egress: push mac */ |
---|
254 | | - skb_push_rcsum(skb2, skb->mac_len); |
---|
| 283 | + /* target device/action expect data at mac */ |
---|
| 284 | + skb_push_rcsum(skb2, mac_len); |
---|
255 | 285 | } |
---|
256 | 286 | } |
---|
257 | 287 | |
---|
.. | .. |
---|
260 | 290 | |
---|
261 | 291 | /* mirror is always swallowed */ |
---|
262 | 292 | if (is_redirect) { |
---|
263 | | - skb2->tc_redirected = 1; |
---|
264 | | - skb2->tc_from_ingress = skb2->tc_at_ingress; |
---|
| 293 | + skb_set_redirected(skb2, skb2->tc_at_ingress); |
---|
265 | 294 | |
---|
266 | 295 | /* let's the caller reinsert the packet, if possible */ |
---|
267 | 296 | if (use_reinsert) { |
---|
268 | 297 | res->ingress = want_ingress; |
---|
269 | | - res->qstats = this_cpu_ptr(m->common.cpu_qstats); |
---|
270 | | - return TC_ACT_REINSERT; |
---|
| 298 | + if (skb_tc_reinsert(skb, res)) |
---|
| 299 | + tcf_action_inc_overlimit_qstats(&m->common); |
---|
| 300 | + __this_cpu_dec(mirred_rec_level); |
---|
| 301 | + return TC_ACT_CONSUMED; |
---|
271 | 302 | } |
---|
272 | 303 | } |
---|
273 | 304 | |
---|
.. | .. |
---|
278 | 309 | |
---|
279 | 310 | if (err) { |
---|
280 | 311 | out: |
---|
281 | | - qstats_overlimit_inc(this_cpu_ptr(m->common.cpu_qstats)); |
---|
| 312 | + tcf_action_inc_overlimit_qstats(&m->common); |
---|
282 | 313 | if (tcf_mirred_is_act_redirect(m_eaction)) |
---|
283 | 314 | retval = TC_ACT_SHOT; |
---|
284 | 315 | } |
---|
| 316 | + __this_cpu_dec(mirred_rec_level); |
---|
285 | 317 | |
---|
286 | 318 | return retval; |
---|
287 | 319 | } |
---|
288 | 320 | |
---|
289 | | -static void tcf_stats_update(struct tc_action *a, u64 bytes, u32 packets, |
---|
290 | | - u64 lastuse) |
---|
| 321 | +static void tcf_stats_update(struct tc_action *a, u64 bytes, u64 packets, |
---|
| 322 | + u64 drops, u64 lastuse, bool hw) |
---|
291 | 323 | { |
---|
292 | 324 | struct tcf_mirred *m = to_mirred(a); |
---|
293 | 325 | struct tcf_t *tm = &m->tcf_tm; |
---|
294 | 326 | |
---|
295 | | - _bstats_cpu_update(this_cpu_ptr(a->cpu_bstats), bytes, packets); |
---|
| 327 | + tcf_action_update_stats(a, bytes, packets, drops, hw); |
---|
296 | 328 | tm->lastuse = max_t(u64, tm->lastuse, lastuse); |
---|
297 | 329 | } |
---|
298 | 330 | |
---|
.. | .. |
---|
342 | 374 | return tcf_generic_walker(tn, skb, cb, type, ops, extack); |
---|
343 | 375 | } |
---|
344 | 376 | |
---|
345 | | -static int tcf_mirred_search(struct net *net, struct tc_action **a, u32 index, |
---|
346 | | - struct netlink_ext_ack *extack) |
---|
| 377 | +static int tcf_mirred_search(struct net *net, struct tc_action **a, u32 index) |
---|
347 | 378 | { |
---|
348 | 379 | struct tc_action_net *tn = net_generic(net, mirred_net_id); |
---|
349 | 380 | |
---|
.. | .. |
---|
380 | 411 | .notifier_call = mirred_device_event, |
---|
381 | 412 | }; |
---|
382 | 413 | |
---|
383 | | -static struct net_device *tcf_mirred_get_dev(const struct tc_action *a) |
---|
| 414 | +static void tcf_mirred_dev_put(void *priv) |
---|
| 415 | +{ |
---|
| 416 | + struct net_device *dev = priv; |
---|
| 417 | + |
---|
| 418 | + dev_put(dev); |
---|
| 419 | +} |
---|
| 420 | + |
---|
| 421 | +static struct net_device * |
---|
| 422 | +tcf_mirred_get_dev(const struct tc_action *a, |
---|
| 423 | + tc_action_priv_destructor *destructor) |
---|
384 | 424 | { |
---|
385 | 425 | struct tcf_mirred *m = to_mirred(a); |
---|
386 | 426 | struct net_device *dev; |
---|
387 | 427 | |
---|
388 | 428 | rcu_read_lock(); |
---|
389 | 429 | dev = rcu_dereference(m->tcfm_dev); |
---|
390 | | - if (dev) |
---|
| 430 | + if (dev) { |
---|
391 | 431 | dev_hold(dev); |
---|
| 432 | + *destructor = tcf_mirred_dev_put; |
---|
| 433 | + } |
---|
392 | 434 | rcu_read_unlock(); |
---|
393 | 435 | |
---|
394 | 436 | return dev; |
---|
395 | 437 | } |
---|
396 | 438 | |
---|
397 | | -static void tcf_mirred_put_dev(struct net_device *dev) |
---|
| 439 | +static size_t tcf_mirred_get_fill_size(const struct tc_action *act) |
---|
398 | 440 | { |
---|
399 | | - dev_put(dev); |
---|
| 441 | + return nla_total_size(sizeof(struct tc_mirred)); |
---|
400 | 442 | } |
---|
401 | 443 | |
---|
402 | 444 | static struct tc_action_ops act_mirred_ops = { |
---|
403 | 445 | .kind = "mirred", |
---|
404 | | - .type = TCA_ACT_MIRRED, |
---|
| 446 | + .id = TCA_ID_MIRRED, |
---|
405 | 447 | .owner = THIS_MODULE, |
---|
406 | 448 | .act = tcf_mirred_act, |
---|
407 | 449 | .stats_update = tcf_stats_update, |
---|
.. | .. |
---|
410 | 452 | .init = tcf_mirred_init, |
---|
411 | 453 | .walk = tcf_mirred_walker, |
---|
412 | 454 | .lookup = tcf_mirred_search, |
---|
| 455 | + .get_fill_size = tcf_mirred_get_fill_size, |
---|
413 | 456 | .size = sizeof(struct tcf_mirred), |
---|
414 | 457 | .get_dev = tcf_mirred_get_dev, |
---|
415 | | - .put_dev = tcf_mirred_put_dev, |
---|
416 | 458 | }; |
---|
417 | 459 | |
---|
418 | 460 | static __net_init int mirred_init_net(struct net *net) |
---|