linux/net/sched/act_pedit.c
<<
>>
Prefs
   1/*
   2 * net/sched/act_pedit.c        Generic packet editor
   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 *
   9 * Authors:     Jamal Hadi Salim (2002-4)
  10 */
  11
  12#include <linux/types.h>
  13#include <linux/kernel.h>
  14#include <linux/string.h>
  15#include <linux/errno.h>
  16#include <linux/skbuff.h>
  17#include <linux/rtnetlink.h>
  18#include <linux/module.h>
  19#include <linux/init.h>
  20#include <linux/slab.h>
  21#include <net/netlink.h>
  22#include <net/pkt_sched.h>
  23#include <linux/tc_act/tc_pedit.h>
  24#include <net/tc_act/tc_pedit.h>
  25#include <uapi/linux/tc_act/tc_pedit.h>
  26#include <net/pkt_cls.h>
  27
  28static unsigned int pedit_net_id;
  29static struct tc_action_ops act_pedit_ops;
  30
  31static const struct nla_policy pedit_policy[TCA_PEDIT_MAX + 1] = {
  32        [TCA_PEDIT_PARMS]       = { .len = sizeof(struct tc_pedit) },
  33        [TCA_PEDIT_KEYS_EX]   = { .type = NLA_NESTED },
  34};
  35
  36static const struct nla_policy pedit_key_ex_policy[TCA_PEDIT_KEY_EX_MAX + 1] = {
  37        [TCA_PEDIT_KEY_EX_HTYPE]  = { .type = NLA_U16 },
  38        [TCA_PEDIT_KEY_EX_CMD]    = { .type = NLA_U16 },
  39};
  40
  41static struct tcf_pedit_key_ex *tcf_pedit_keys_ex_parse(struct nlattr *nla,
  42                                                        u8 n)
  43{
  44        struct tcf_pedit_key_ex *keys_ex;
  45        struct tcf_pedit_key_ex *k;
  46        const struct nlattr *ka;
  47        int err = -EINVAL;
  48        int rem;
  49
  50        if (!nla)
  51                return NULL;
  52
  53        keys_ex = kcalloc(n, sizeof(*k), GFP_KERNEL);
  54        if (!keys_ex)
  55                return ERR_PTR(-ENOMEM);
  56
  57        k = keys_ex;
  58
  59        nla_for_each_nested(ka, nla, rem) {
  60                struct nlattr *tb[TCA_PEDIT_KEY_EX_MAX + 1];
  61
  62                if (!n) {
  63                        err = -EINVAL;
  64                        goto err_out;
  65                }
  66                n--;
  67
  68                if (nla_type(ka) != TCA_PEDIT_KEY_EX) {
  69                        err = -EINVAL;
  70                        goto err_out;
  71                }
  72
  73                err = nla_parse_nested_deprecated(tb, TCA_PEDIT_KEY_EX_MAX,
  74                                                  ka, pedit_key_ex_policy,
  75                                                  NULL);
  76                if (err)
  77                        goto err_out;
  78
  79                if (!tb[TCA_PEDIT_KEY_EX_HTYPE] ||
  80                    !tb[TCA_PEDIT_KEY_EX_CMD]) {
  81                        err = -EINVAL;
  82                        goto err_out;
  83                }
  84
  85                k->htype = nla_get_u16(tb[TCA_PEDIT_KEY_EX_HTYPE]);
  86                k->cmd = nla_get_u16(tb[TCA_PEDIT_KEY_EX_CMD]);
  87
  88                if (k->htype > TCA_PEDIT_HDR_TYPE_MAX ||
  89                    k->cmd > TCA_PEDIT_CMD_MAX) {
  90                        err = -EINVAL;
  91                        goto err_out;
  92                }
  93
  94                k++;
  95        }
  96
  97        if (n) {
  98                err = -EINVAL;
  99                goto err_out;
 100        }
 101
 102        return keys_ex;
 103
 104err_out:
 105        kfree(keys_ex);
 106        return ERR_PTR(err);
 107}
 108
 109static int tcf_pedit_key_ex_dump(struct sk_buff *skb,
 110                                 struct tcf_pedit_key_ex *keys_ex, int n)
 111{
 112        struct nlattr *keys_start = nla_nest_start_noflag(skb,
 113                                                          TCA_PEDIT_KEYS_EX);
 114
 115        if (!keys_start)
 116                goto nla_failure;
 117        for (; n > 0; n--) {
 118                struct nlattr *key_start;
 119
 120                key_start = nla_nest_start_noflag(skb, TCA_PEDIT_KEY_EX);
 121                if (!key_start)
 122                        goto nla_failure;
 123
 124                if (nla_put_u16(skb, TCA_PEDIT_KEY_EX_HTYPE, keys_ex->htype) ||
 125                    nla_put_u16(skb, TCA_PEDIT_KEY_EX_CMD, keys_ex->cmd))
 126                        goto nla_failure;
 127
 128                nla_nest_end(skb, key_start);
 129
 130                keys_ex++;
 131        }
 132
 133        nla_nest_end(skb, keys_start);
 134
 135        return 0;
 136nla_failure:
 137        nla_nest_cancel(skb, keys_start);
 138        return -EINVAL;
 139}
 140
 141static int tcf_pedit_init(struct net *net, struct nlattr *nla,
 142                          struct nlattr *est, struct tc_action **a,
 143                          int ovr, int bind, bool rtnl_held,
 144                          struct tcf_proto *tp, u32 flags,
 145                          struct netlink_ext_ack *extack)
 146{
 147        struct tc_action_net *tn = net_generic(net, pedit_net_id);
 148        struct nlattr *tb[TCA_PEDIT_MAX + 1];
 149        struct tcf_chain *goto_ch = NULL;
 150        struct tc_pedit_key *keys = NULL;
 151        struct tcf_pedit_key_ex *keys_ex;
 152        struct tc_pedit *parm;
 153        struct nlattr *pattr;
 154        struct tcf_pedit *p;
 155        int ret = 0, err;
 156        int ksize;
 157        u32 index;
 158
 159        if (!nla) {
 160                NL_SET_ERR_MSG_MOD(extack, "Pedit requires attributes to be passed");
 161                return -EINVAL;
 162        }
 163
 164        err = nla_parse_nested_deprecated(tb, TCA_PEDIT_MAX, nla,
 165                                          pedit_policy, NULL);
 166        if (err < 0)
 167                return err;
 168
 169        pattr = tb[TCA_PEDIT_PARMS];
 170        if (!pattr)
 171                pattr = tb[TCA_PEDIT_PARMS_EX];
 172        if (!pattr) {
 173                NL_SET_ERR_MSG_MOD(extack, "Missing required TCA_PEDIT_PARMS or TCA_PEDIT_PARMS_EX pedit attribute");
 174                return -EINVAL;
 175        }
 176
 177        parm = nla_data(pattr);
 178        if (!parm->nkeys) {
 179                NL_SET_ERR_MSG_MOD(extack, "Pedit requires keys to be passed");
 180                return -EINVAL;
 181        }
 182        ksize = parm->nkeys * sizeof(struct tc_pedit_key);
 183        if (nla_len(pattr) < sizeof(*parm) + ksize) {
 184                NL_SET_ERR_MSG_ATTR(extack, pattr, "Length of TCA_PEDIT_PARMS or TCA_PEDIT_PARMS_EX pedit attribute is invalid");
 185                return -EINVAL;
 186        }
 187
 188        keys_ex = tcf_pedit_keys_ex_parse(tb[TCA_PEDIT_KEYS_EX], parm->nkeys);
 189        if (IS_ERR(keys_ex))
 190                return PTR_ERR(keys_ex);
 191
 192        index = parm->index;
 193        err = tcf_idr_check_alloc(tn, &index, a, bind);
 194        if (!err) {
 195                ret = tcf_idr_create(tn, index, est, a,
 196                                     &act_pedit_ops, bind, false, 0);
 197                if (ret) {
 198                        tcf_idr_cleanup(tn, index);
 199                        goto out_free;
 200                }
 201                ret = ACT_P_CREATED;
 202        } else if (err > 0) {
 203                if (bind)
 204                        goto out_free;
 205                if (!ovr) {
 206                        ret = -EEXIST;
 207                        goto out_release;
 208                }
 209        } else {
 210                ret = err;
 211                goto out_free;
 212        }
 213
 214        err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
 215        if (err < 0) {
 216                ret = err;
 217                goto out_release;
 218        }
 219        p = to_pedit(*a);
 220        spin_lock_bh(&p->tcf_lock);
 221
 222        if (ret == ACT_P_CREATED ||
 223            (p->tcfp_nkeys && p->tcfp_nkeys != parm->nkeys)) {
 224                keys = kmalloc(ksize, GFP_ATOMIC);
 225                if (!keys) {
 226                        spin_unlock_bh(&p->tcf_lock);
 227                        ret = -ENOMEM;
 228                        goto put_chain;
 229                }
 230                kfree(p->tcfp_keys);
 231                p->tcfp_keys = keys;
 232                p->tcfp_nkeys = parm->nkeys;
 233        }
 234        memcpy(p->tcfp_keys, parm->keys, ksize);
 235
 236        p->tcfp_flags = parm->flags;
 237        goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
 238
 239        kfree(p->tcfp_keys_ex);
 240        p->tcfp_keys_ex = keys_ex;
 241
 242        spin_unlock_bh(&p->tcf_lock);
 243        if (goto_ch)
 244                tcf_chain_put_by_act(goto_ch);
 245        return ret;
 246
 247put_chain:
 248        if (goto_ch)
 249                tcf_chain_put_by_act(goto_ch);
 250out_release:
 251        tcf_idr_release(*a, bind);
 252out_free:
 253        kfree(keys_ex);
 254        return ret;
 255
 256}
 257
 258static void tcf_pedit_cleanup(struct tc_action *a)
 259{
 260        struct tcf_pedit *p = to_pedit(a);
 261        struct tc_pedit_key *keys = p->tcfp_keys;
 262
 263        kfree(keys);
 264        kfree(p->tcfp_keys_ex);
 265}
 266
 267static bool offset_valid(struct sk_buff *skb, int offset)
 268{
 269        if (offset > 0 && offset > skb->len)
 270                return false;
 271
 272        if  (offset < 0 && -offset > skb_headroom(skb))
 273                return false;
 274
 275        return true;
 276}
 277
 278static int pedit_skb_hdr_offset(struct sk_buff *skb,
 279                                enum pedit_header_type htype, int *hoffset)
 280{
 281        int ret = -EINVAL;
 282
 283        switch (htype) {
 284        case TCA_PEDIT_KEY_EX_HDR_TYPE_ETH:
 285                if (skb_mac_header_was_set(skb)) {
 286                        *hoffset = skb_mac_offset(skb);
 287                        ret = 0;
 288                }
 289                break;
 290        case TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK:
 291        case TCA_PEDIT_KEY_EX_HDR_TYPE_IP4:
 292        case TCA_PEDIT_KEY_EX_HDR_TYPE_IP6:
 293                *hoffset = skb_network_offset(skb);
 294                ret = 0;
 295                break;
 296        case TCA_PEDIT_KEY_EX_HDR_TYPE_TCP:
 297        case TCA_PEDIT_KEY_EX_HDR_TYPE_UDP:
 298                if (skb_transport_header_was_set(skb)) {
 299                        *hoffset = skb_transport_offset(skb);
 300                        ret = 0;
 301                }
 302                break;
 303        default:
 304                ret = -EINVAL;
 305                break;
 306        }
 307
 308        return ret;
 309}
 310
 311static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
 312                         struct tcf_result *res)
 313{
 314        struct tcf_pedit *p = to_pedit(a);
 315        int i;
 316
 317        if (skb_unclone(skb, GFP_ATOMIC))
 318                return p->tcf_action;
 319
 320        spin_lock(&p->tcf_lock);
 321
 322        tcf_lastuse_update(&p->tcf_tm);
 323
 324        if (p->tcfp_nkeys > 0) {
 325                struct tc_pedit_key *tkey = p->tcfp_keys;
 326                struct tcf_pedit_key_ex *tkey_ex = p->tcfp_keys_ex;
 327                enum pedit_header_type htype =
 328                        TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK;
 329                enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET;
 330
 331                for (i = p->tcfp_nkeys; i > 0; i--, tkey++) {
 332                        u32 *ptr, hdata;
 333                        int offset = tkey->off;
 334                        int hoffset;
 335                        u32 val;
 336                        int rc;
 337
 338                        if (tkey_ex) {
 339                                htype = tkey_ex->htype;
 340                                cmd = tkey_ex->cmd;
 341
 342                                tkey_ex++;
 343                        }
 344
 345                        rc = pedit_skb_hdr_offset(skb, htype, &hoffset);
 346                        if (rc) {
 347                                pr_info("tc action pedit bad header type specified (0x%x)\n",
 348                                        htype);
 349                                goto bad;
 350                        }
 351
 352                        if (tkey->offmask) {
 353                                u8 *d, _d;
 354
 355                                if (!offset_valid(skb, hoffset + tkey->at)) {
 356                                        pr_info("tc action pedit 'at' offset %d out of bounds\n",
 357                                                hoffset + tkey->at);
 358                                        goto bad;
 359                                }
 360                                d = skb_header_pointer(skb, hoffset + tkey->at,
 361                                                       sizeof(_d), &_d);
 362                                if (!d)
 363                                        goto bad;
 364                                offset += (*d & tkey->offmask) >> tkey->shift;
 365                        }
 366
 367                        if (offset % 4) {
 368                                pr_info("tc action pedit offset must be on 32 bit boundaries\n");
 369                                goto bad;
 370                        }
 371
 372                        if (!offset_valid(skb, hoffset + offset)) {
 373                                pr_info("tc action pedit offset %d out of bounds\n",
 374                                        hoffset + offset);
 375                                goto bad;
 376                        }
 377
 378                        ptr = skb_header_pointer(skb, hoffset + offset,
 379                                                 sizeof(hdata), &hdata);
 380                        if (!ptr)
 381                                goto bad;
 382                        /* just do it, baby */
 383                        switch (cmd) {
 384                        case TCA_PEDIT_KEY_EX_CMD_SET:
 385                                val = tkey->val;
 386                                break;
 387                        case TCA_PEDIT_KEY_EX_CMD_ADD:
 388                                val = (*ptr + tkey->val) & ~tkey->mask;
 389                                break;
 390                        default:
 391                                pr_info("tc action pedit bad command (%d)\n",
 392                                        cmd);
 393                                goto bad;
 394                        }
 395
 396                        *ptr = ((*ptr & tkey->mask) ^ val);
 397                        if (ptr == &hdata)
 398                                skb_store_bits(skb, hoffset + offset, ptr, 4);
 399                }
 400
 401                goto done;
 402        } else {
 403                WARN(1, "pedit BUG: index %d\n", p->tcf_index);
 404        }
 405
 406bad:
 407        p->tcf_qstats.overlimits++;
 408done:
 409        bstats_update(&p->tcf_bstats, skb);
 410        spin_unlock(&p->tcf_lock);
 411        return p->tcf_action;
 412}
 413
 414static void tcf_pedit_stats_update(struct tc_action *a, u64 bytes, u64 packets,
 415                                   u64 drops, u64 lastuse, bool hw)
 416{
 417        struct tcf_pedit *d = to_pedit(a);
 418        struct tcf_t *tm = &d->tcf_tm;
 419
 420        tcf_action_update_stats(a, bytes, packets, drops, hw);
 421        tm->lastuse = max_t(u64, tm->lastuse, lastuse);
 422}
 423
 424static int tcf_pedit_dump(struct sk_buff *skb, struct tc_action *a,
 425                          int bind, int ref)
 426{
 427        unsigned char *b = skb_tail_pointer(skb);
 428        struct tcf_pedit *p = to_pedit(a);
 429        struct tc_pedit *opt;
 430        struct tcf_t t;
 431        int s;
 432
 433        s = struct_size(opt, keys, p->tcfp_nkeys);
 434
 435        /* netlink spinlocks held above us - must use ATOMIC */
 436        opt = kzalloc(s, GFP_ATOMIC);
 437        if (unlikely(!opt))
 438                return -ENOBUFS;
 439
 440        spin_lock_bh(&p->tcf_lock);
 441        memcpy(opt->keys, p->tcfp_keys, flex_array_size(opt, keys, p->tcfp_nkeys));
 442        opt->index = p->tcf_index;
 443        opt->nkeys = p->tcfp_nkeys;
 444        opt->flags = p->tcfp_flags;
 445        opt->action = p->tcf_action;
 446        opt->refcnt = refcount_read(&p->tcf_refcnt) - ref;
 447        opt->bindcnt = atomic_read(&p->tcf_bindcnt) - bind;
 448
 449        if (p->tcfp_keys_ex) {
 450                if (tcf_pedit_key_ex_dump(skb,
 451                                          p->tcfp_keys_ex,
 452                                          p->tcfp_nkeys))
 453                        goto nla_put_failure;
 454
 455                if (nla_put(skb, TCA_PEDIT_PARMS_EX, s, opt))
 456                        goto nla_put_failure;
 457        } else {
 458                if (nla_put(skb, TCA_PEDIT_PARMS, s, opt))
 459                        goto nla_put_failure;
 460        }
 461
 462        tcf_tm_dump(&t, &p->tcf_tm);
 463        if (nla_put_64bit(skb, TCA_PEDIT_TM, sizeof(t), &t, TCA_PEDIT_PAD))
 464                goto nla_put_failure;
 465        spin_unlock_bh(&p->tcf_lock);
 466
 467        kfree(opt);
 468        return skb->len;
 469
 470nla_put_failure:
 471        spin_unlock_bh(&p->tcf_lock);
 472        nlmsg_trim(skb, b);
 473        kfree(opt);
 474        return -1;
 475}
 476
 477static int tcf_pedit_walker(struct net *net, struct sk_buff *skb,
 478                            struct netlink_callback *cb, int type,
 479                            const struct tc_action_ops *ops,
 480                            struct netlink_ext_ack *extack)
 481{
 482        struct tc_action_net *tn = net_generic(net, pedit_net_id);
 483
 484        return tcf_generic_walker(tn, skb, cb, type, ops, extack);
 485}
 486
 487static int tcf_pedit_search(struct net *net, struct tc_action **a, u32 index)
 488{
 489        struct tc_action_net *tn = net_generic(net, pedit_net_id);
 490
 491        return tcf_idr_search(tn, a, index);
 492}
 493
 494static struct tc_action_ops act_pedit_ops = {
 495        .kind           =       "pedit",
 496        .id             =       TCA_ID_PEDIT,
 497        .owner          =       THIS_MODULE,
 498        .act            =       tcf_pedit_act,
 499        .stats_update   =       tcf_pedit_stats_update,
 500        .dump           =       tcf_pedit_dump,
 501        .cleanup        =       tcf_pedit_cleanup,
 502        .init           =       tcf_pedit_init,
 503        .walk           =       tcf_pedit_walker,
 504        .lookup         =       tcf_pedit_search,
 505        .size           =       sizeof(struct tcf_pedit),
 506};
 507
 508static __net_init int pedit_init_net(struct net *net)
 509{
 510        struct tc_action_net *tn = net_generic(net, pedit_net_id);
 511
 512        return tc_action_net_init(net, tn, &act_pedit_ops);
 513}
 514
 515static void __net_exit pedit_exit_net(struct list_head *net_list)
 516{
 517        tc_action_net_exit(net_list, pedit_net_id);
 518}
 519
 520static struct pernet_operations pedit_net_ops = {
 521        .init = pedit_init_net,
 522        .exit_batch = pedit_exit_net,
 523        .id   = &pedit_net_id,
 524        .size = sizeof(struct tc_action_net),
 525};
 526
 527MODULE_AUTHOR("Jamal Hadi Salim(2002-4)");
 528MODULE_DESCRIPTION("Generic Packet Editor actions");
 529MODULE_LICENSE("GPL");
 530
 531static int __init pedit_init_module(void)
 532{
 533        return tcf_register_action(&act_pedit_ops, &pedit_net_ops);
 534}
 535
 536static void __exit pedit_cleanup_module(void)
 537{
 538        tcf_unregister_action(&act_pedit_ops, &pedit_net_ops);
 539}
 540
 541module_init(pedit_init_module);
 542module_exit(pedit_cleanup_module);
 543