| 1 | |
| 2 | |
| 3 | * net/sched/act_sample.c - Packet sampling tc action
|
| 4 | * Copyright (c) 2017 Yotam Gigi <[email protected]>
|
| 5 |
|
| 6 |
|
| 7 | #include <linux/types.h>
|
| 8 | #include <linux/kernel.h>
|
| 9 | #include <linux/string.h>
|
| 10 | #include <linux/errno.h>
|
| 11 | #include <linux/skbuff.h>
|
| 12 | #include <linux/rtnetlink.h>
|
| 13 | #include <linux/module.h>
|
| 14 | #include <linux/init.h>
|
| 15 | #include <linux/gfp.h>
|
| 16 | #include <net/net_namespace.h>
|
| 17 | #include <net/netlink.h>
|
| 18 | #include <net/pkt_sched.h>
|
| 19 | #include <linux/tc_act/tc_sample.h>
|
| 20 | #include <net/tc_act/tc_sample.h>
|
| 21 | #include <net/psample.h>
|
| 22 | #include <net/pkt_cls.h>
|
| 23 | #include <net/tc_wrapper.h>
|
| 24 |
|
| 25 | #include <linux/if_arp.h>
|
| 26 |
|
| 27 | static struct tc_action_ops act_sample_ops;
|
| 28 |
|
| 29 | static const struct nla_policy sample_policy[TCA_SAMPLE_MAX + 1] = {
|
| 30 | [TCA_SAMPLE_PARMS] = { .len = sizeof(struct tc_sample) },
|
| 31 | [TCA_SAMPLE_RATE] = { .type = NLA_U32 },
|
| 32 | [TCA_SAMPLE_TRUNC_SIZE] = { .type = NLA_U32 },
|
| 33 | [TCA_SAMPLE_PSAMPLE_GROUP] = { .type = NLA_U32 },
|
| 34 | };
|
| 35 |
|
| 36 | static int tcf_sample_init(struct net *net, struct nlattr *nla,
|
| 37 | struct nlattr *est, struct tc_action **a,
|
| 38 | struct tcf_proto *tp,
|
| 39 | u32 flags, struct netlink_ext_ack *extack)
|
| 40 | {
|
| 41 | struct tc_action_net *tn = net_generic(net, act_sample_ops.net_id);
|
| 42 | bool bind = flags & TCA_ACT_FLAGS_BIND;
|
| 43 | struct nlattr *tb[TCA_SAMPLE_MAX + 1];
|
| 44 | struct psample_group *psample_group;
|
| 45 | u32 psample_group_num, rate, index;
|
| 46 | struct tcf_chain *goto_ch = NULL;
|
| 47 | struct tc_sample *parm;
|
| 48 | struct tcf_sample *s;
|
| 49 | bool exists = false;
|
| 50 | int ret, err;
|
| 51 |
|
| 52 | if (!nla)
|
| 53 | return -EINVAL;
|
| 54 | ret = nla_parse_nested_deprecated(tb, TCA_SAMPLE_MAX, nla,
|
| 55 | sample_policy, NULL);
|
| 56 | if (ret < 0)
|
| 57 | return ret;
|
| 58 |
|
| 59 | if (!tb[TCA_SAMPLE_PARMS])
|
| 60 | return -EINVAL;
|
| 61 |
|
| 62 | parm = nla_data(tb[TCA_SAMPLE_PARMS]);
|
| 63 | index = parm->index;
|
| 64 | err = tcf_idr_check_alloc(tn, &index, a, bind);
|
| 65 | if (err < 0)
|
| 66 | return err;
|
| 67 | exists = err;
|
| 68 | if (exists && bind)
|
| 69 | return ACT_P_BOUND;
|
| 70 |
|
| 71 | if (!exists) {
|
| 72 | ret = tcf_idr_create(tn, index, est, a,
|
| 73 | &act_sample_ops, bind, true, flags);
|
| 74 | if (ret) {
|
| 75 | tcf_idr_cleanup(tn, index);
|
| 76 | return ret;
|
| 77 | }
|
| 78 | ret = ACT_P_CREATED;
|
| 79 | } else if (!(flags & TCA_ACT_FLAGS_REPLACE)) {
|
| 80 | tcf_idr_release(*a, bind);
|
| 81 | return -EEXIST;
|
| 82 | }
|
| 83 |
|
| 84 | if (!tb[TCA_SAMPLE_RATE] || !tb[TCA_SAMPLE_PSAMPLE_GROUP]) {
|
| 85 | NL_SET_ERR_MSG(extack, "sample rate and group are required");
|
| 86 | err = -EINVAL;
|
| 87 | goto release_idr;
|
| 88 | }
|
| 89 |
|
| 90 | err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
|
| 91 | if (err < 0)
|
| 92 | goto release_idr;
|
| 93 |
|
| 94 | rate = nla_get_u32(tb[TCA_SAMPLE_RATE]);
|
| 95 | if (!rate) {
|
| 96 | NL_SET_ERR_MSG(extack, "invalid sample rate");
|
| 97 | err = -EINVAL;
|
| 98 | goto put_chain;
|
| 99 | }
|
| 100 | psample_group_num = nla_get_u32(tb[TCA_SAMPLE_PSAMPLE_GROUP]);
|
| 101 | psample_group = psample_group_get(net, psample_group_num);
|
| 102 | if (!psample_group) {
|
| 103 | err = -ENOMEM;
|
| 104 | goto put_chain;
|
| 105 | }
|
| 106 |
|
| 107 | s = to_sample(*a);
|
| 108 |
|
| 109 | spin_lock_bh(&s->tcf_lock);
|
| 110 | goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
|
| 111 | s->rate = rate;
|
| 112 | s->psample_group_num = psample_group_num;
|
| 113 | psample_group = rcu_replace_pointer(s->psample_group, psample_group,
|
| 114 | lockdep_is_held(&s->tcf_lock));
|
| 115 |
|
| 116 | if (tb[TCA_SAMPLE_TRUNC_SIZE]) {
|
| 117 | s->truncate = true;
|
| 118 | s->trunc_size = nla_get_u32(tb[TCA_SAMPLE_TRUNC_SIZE]);
|
| 119 | }
|
| 120 | spin_unlock_bh(&s->tcf_lock);
|
| 121 |
|
| 122 | if (psample_group)
|
| 123 | psample_group_put(psample_group);
|
| 124 | if (goto_ch)
|
| 125 | tcf_chain_put_by_act(goto_ch);
|
| 126 |
|
| 127 | return ret;
|
| 128 | put_chain:
|
| 129 | if (goto_ch)
|
| 130 | tcf_chain_put_by_act(goto_ch);
|
| 131 | release_idr:
|
| 132 | tcf_idr_release(*a, bind);
|
| 133 | return err;
|
| 134 | }
|
| 135 |
|
| 136 | static void tcf_sample_cleanup(struct tc_action *a)
|
| 137 | {
|
| 138 | struct tcf_sample *s = to_sample(a);
|
| 139 | struct psample_group *psample_group;
|
| 140 |
|
| 141 |
|
| 142 | psample_group = rcu_dereference_protected(s->psample_group, 1);
|
| 143 | RCU_INIT_POINTER(s->psample_group, NULL);
|
| 144 | if (psample_group)
|
| 145 | psample_group_put(psample_group);
|
| 146 | }
|
| 147 |
|
| 148 | static bool tcf_sample_dev_ok_push(struct net_device *dev)
|
| 149 | {
|
| 150 | switch (dev->type) {
|
| 151 | case ARPHRD_TUNNEL:
|
| 152 | case ARPHRD_TUNNEL6:
|
| 153 | case ARPHRD_SIT:
|
| 154 | case ARPHRD_IPGRE:
|
| 155 | case ARPHRD_IP6GRE:
|
| 156 | case ARPHRD_VOID:
|
| 157 | case ARPHRD_NONE:
|
| 158 | return false;
|
| 159 | default:
|
| 160 | return true;
|
| 161 | }
|
| 162 | }
|
| 163 |
|
| 164 | TC_INDIRECT_SCOPE int tcf_sample_act(struct sk_buff *skb,
|
| 165 | const struct tc_action *a,
|
| 166 | struct tcf_result *res)
|
| 167 | {
|
| 168 | struct tcf_sample *s = to_sample(a);
|
| 169 | struct psample_group *psample_group;
|
| 170 | u8 cookie_data[TC_COOKIE_MAX_SIZE];
|
| 171 | struct psample_metadata md = {};
|
| 172 | struct tc_cookie *user_cookie;
|
| 173 | int retval;
|
| 174 |
|
| 175 | tcf_lastuse_update(&s->tcf_tm);
|
| 176 | bstats_update(this_cpu_ptr(s->common.cpu_bstats), skb);
|
| 177 | retval = READ_ONCE(s->tcf_action);
|
| 178 |
|
| 179 | psample_group = rcu_dereference_bh(s->psample_group);
|
| 180 |
|
| 181 |
|
| 182 | if (psample_group && (get_random_u32_below(s->rate) == 0)) {
|
| 183 | if (!skb_at_tc_ingress(skb)) {
|
| 184 | md.in_ifindex = skb->skb_iif;
|
| 185 | md.out_ifindex = skb->dev->ifindex;
|
| 186 | } else {
|
| 187 | md.in_ifindex = skb->dev->ifindex;
|
| 188 | }
|
| 189 |
|
| 190 |
|
| 191 | if (skb_at_tc_ingress(skb) && tcf_sample_dev_ok_push(skb->dev))
|
| 192 | skb_push(skb, skb->mac_len);
|
| 193 |
|
| 194 | rcu_read_lock();
|
| 195 | user_cookie = rcu_dereference(a->user_cookie);
|
| 196 | if (user_cookie) {
|
| 197 | memcpy(cookie_data, user_cookie->data,
|
| 198 | user_cookie->len);
|
| 199 | md.user_cookie = cookie_data;
|
| 200 | md.user_cookie_len = user_cookie->len;
|
| 201 | }
|
| 202 | rcu_read_unlock();
|
| 203 |
|
| 204 | md.trunc_size = s->truncate ? s->trunc_size : skb->len;
|
| 205 | psample_sample_packet(psample_group, skb, s->rate, &md);
|
| 206 |
|
| 207 | if (skb_at_tc_ingress(skb) && tcf_sample_dev_ok_push(skb->dev))
|
| 208 | skb_pull(skb, skb->mac_len);
|
| 209 | }
|
| 210 |
|
| 211 | return retval;
|
| 212 | }
|
| 213 |
|
| 214 | static void tcf_sample_stats_update(struct tc_action *a, u64 bytes, u64 packets,
|
| 215 | u64 drops, u64 lastuse, bool hw)
|
| 216 | {
|
| 217 | struct tcf_sample *s = to_sample(a);
|
| 218 | struct tcf_t *tm = &s->tcf_tm;
|
| 219 |
|
| 220 | tcf_action_update_stats(a, bytes, packets, drops, hw);
|
| 221 | tm->lastuse = max_t(u64, tm->lastuse, lastuse);
|
| 222 | }
|
| 223 |
|
| 224 | static int tcf_sample_dump(struct sk_buff *skb, struct tc_action *a,
|
| 225 | int bind, int ref)
|
| 226 | {
|
| 227 | unsigned char *b = skb_tail_pointer(skb);
|
| 228 | struct tcf_sample *s = to_sample(a);
|
| 229 | struct tc_sample opt = {
|
| 230 | .index = s->tcf_index,
|
| 231 | .refcnt = refcount_read(&s->tcf_refcnt) - ref,
|
| 232 | .bindcnt = atomic_read(&s->tcf_bindcnt) - bind,
|
| 233 | };
|
| 234 | struct tcf_t t;
|
| 235 |
|
| 236 | spin_lock_bh(&s->tcf_lock);
|
| 237 | opt.action = s->tcf_action;
|
| 238 | if (nla_put(skb, TCA_SAMPLE_PARMS, sizeof(opt), &opt))
|
| 239 | goto nla_put_failure;
|
| 240 |
|
| 241 | tcf_tm_dump(&t, &s->tcf_tm);
|
| 242 | if (nla_put_64bit(skb, TCA_SAMPLE_TM, sizeof(t), &t, TCA_SAMPLE_PAD))
|
| 243 | goto nla_put_failure;
|
| 244 |
|
| 245 | if (nla_put_u32(skb, TCA_SAMPLE_RATE, s->rate))
|
| 246 | goto nla_put_failure;
|
| 247 |
|
| 248 | if (s->truncate)
|
| 249 | if (nla_put_u32(skb, TCA_SAMPLE_TRUNC_SIZE, s->trunc_size))
|
| 250 | goto nla_put_failure;
|
| 251 |
|
| 252 | if (nla_put_u32(skb, TCA_SAMPLE_PSAMPLE_GROUP, s->psample_group_num))
|
| 253 | goto nla_put_failure;
|
| 254 | spin_unlock_bh(&s->tcf_lock);
|
| 255 |
|
| 256 | return skb->len;
|
| 257 |
|
| 258 | nla_put_failure:
|
| 259 | spin_unlock_bh(&s->tcf_lock);
|
| 260 | nlmsg_trim(skb, b);
|
| 261 | return -1;
|
| 262 | }
|
| 263 |
|
| 264 | static void tcf_psample_group_put(void *priv)
|
| 265 | {
|
| 266 | struct psample_group *group = priv;
|
| 267 |
|
| 268 | psample_group_put(group);
|
| 269 | }
|
| 270 |
|
| 271 | static struct psample_group *
|
| 272 | tcf_sample_get_group(const struct tc_action *a,
|
| 273 | tc_action_priv_destructor *destructor)
|
| 274 | {
|
| 275 | struct tcf_sample *s = to_sample(a);
|
| 276 | struct psample_group *group;
|
| 277 |
|
| 278 | group = rcu_dereference_protected(s->psample_group,
|
| 279 | lockdep_is_held(&s->tcf_lock));
|
| 280 | if (group) {
|
| 281 | psample_group_take(group);
|
| 282 | *destructor = tcf_psample_group_put;
|
| 283 | }
|
| 284 |
|
| 285 | return group;
|
| 286 | }
|
| 287 |
|
| 288 | static void tcf_offload_sample_get_group(struct flow_action_entry *entry,
|
| 289 | const struct tc_action *act)
|
| 290 | {
|
| 291 | entry->sample.psample_group =
|
| 292 | act->ops->get_psample_group(act, &entry->destructor);
|
| 293 | entry->destructor_priv = entry->sample.psample_group;
|
| 294 | }
|
| 295 |
|
| 296 | static int tcf_sample_offload_act_setup(struct tc_action *act, void *entry_data,
|
| 297 | u32 *index_inc, bool bind,
|
| 298 | struct netlink_ext_ack *extack)
|
| 299 | {
|
| 300 | if (bind) {
|
| 301 | struct flow_action_entry *entry = entry_data;
|
| 302 |
|
| 303 | entry->id = FLOW_ACTION_SAMPLE;
|
| 304 | entry->sample.trunc_size = tcf_sample_trunc_size(act);
|
| 305 | entry->sample.truncate = tcf_sample_truncate(act);
|
| 306 | entry->sample.rate = tcf_sample_rate(act);
|
| 307 | tcf_offload_sample_get_group(entry, act);
|
| 308 | *index_inc = 1;
|
| 309 | } else {
|
| 310 | struct flow_offload_action *fl_action = entry_data;
|
| 311 |
|
| 312 | fl_action->id = FLOW_ACTION_SAMPLE;
|
| 313 | }
|
| 314 |
|
| 315 | return 0;
|
| 316 | }
|
| 317 |
|
| 318 | static size_t tcf_sample_get_fill_size(const struct tc_action *act)
|
| 319 | {
|
| 320 | return nla_total_size(sizeof(struct tc_sample))
|
| 321 | + nla_total_size(sizeof(u32))
|
| 322 | + nla_total_size(sizeof(u32))
|
| 323 | + nla_total_size(sizeof(u32));
|
| 324 | }
|
| 325 |
|
| 326 | static struct tc_action_ops act_sample_ops = {
|
| 327 | .kind = "sample",
|
| 328 | .id = TCA_ID_SAMPLE,
|
| 329 | .owner = THIS_MODULE,
|
| 330 | .act = tcf_sample_act,
|
| 331 | .stats_update = tcf_sample_stats_update,
|
| 332 | .dump = tcf_sample_dump,
|
| 333 | .init = tcf_sample_init,
|
| 334 | .cleanup = tcf_sample_cleanup,
|
| 335 | .get_fill_size = tcf_sample_get_fill_size,
|
| 336 | .get_psample_group = tcf_sample_get_group,
|
| 337 | .offload_act_setup = tcf_sample_offload_act_setup,
|
| 338 | .size = sizeof(struct tcf_sample),
|
| 339 | };
|
| 340 | MODULE_ALIAS_NET_ACT("sample");
|
| 341 |
|
| 342 | static __net_init int sample_init_net(struct net *net)
|
| 343 | {
|
| 344 | struct tc_action_net *tn = net_generic(net, act_sample_ops.net_id);
|
| 345 |
|
| 346 | return tc_action_net_init(net, tn, &act_sample_ops);
|
| 347 | }
|
| 348 |
|
| 349 | static void __net_exit sample_exit_net(struct list_head *net_list)
|
| 350 | {
|
| 351 | tc_action_net_exit(net_list, act_sample_ops.net_id);
|
| 352 | }
|
| 353 |
|
| 354 | static struct pernet_operations sample_net_ops = {
|
| 355 | .init = sample_init_net,
|
| 356 | .exit_batch = sample_exit_net,
|
| 357 | .id = &act_sample_ops.net_id,
|
| 358 | .size = sizeof(struct tc_action_net),
|
| 359 | };
|
| 360 |
|
| 361 | static int __init sample_init_module(void)
|
| 362 | {
|
| 363 | return tcf_register_action(&act_sample_ops, &sample_net_ops);
|
| 364 | }
|
| 365 |
|
| 366 | static void __exit sample_cleanup_module(void)
|
| 367 | {
|
| 368 | tcf_unregister_action(&act_sample_ops, &sample_net_ops);
|
| 369 | }
|
| 370 |
|
| 371 | module_init(sample_init_module);
|
| 372 | module_exit(sample_cleanup_module);
|
| 373 |
|
| 374 | MODULE_AUTHOR("Yotam Gigi <[email protected]>");
|
| 375 | MODULE_DESCRIPTION("Packet sampling action");
|
| 376 | MODULE_LICENSE("GPL v2");
|