tc: add BPF based action
authorJiri Pirko <jiri@resnulli.us>
Thu, 15 Jan 2015 08:52:39 +0000 (09:52 +0100)
committerDavid S. Miller <davem@davemloft.net>
Sun, 18 Jan 2015 04:51:10 +0000 (23:51 -0500)
This action provides a possibility to exec custom BPF code.

Signed-off-by: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/tc_act/tc_bpf.h [new file with mode: 0644]
include/uapi/linux/tc_act/Kbuild
include/uapi/linux/tc_act/tc_bpf.h [new file with mode: 0644]
net/sched/Kconfig
net/sched/Makefile
net/sched/act_bpf.c [new file with mode: 0644]

diff --git a/include/net/tc_act/tc_bpf.h b/include/net/tc_act/tc_bpf.h
new file mode 100644 (file)
index 0000000..86a070f
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2015 Jiri Pirko <jiri@resnulli.us>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __NET_TC_BPF_H
+#define __NET_TC_BPF_H
+
+#include <linux/filter.h>
+#include <net/act_api.h>
+
+struct tcf_bpf {
+       struct tcf_common       common;
+       struct bpf_prog         *filter;
+       struct sock_filter      *bpf_ops;
+       u16                     bpf_num_ops;
+};
+#define to_bpf(a) \
+       container_of(a->priv, struct tcf_bpf, common)
+
+#endif /* __NET_TC_BPF_H */
index b057da2b87a40a788346b09e46aaed27580f16c0..19d5219b0b991eda86a5bb8a0274d35a5a88ce17 100644 (file)
@@ -8,3 +8,4 @@ header-y += tc_nat.h
 header-y += tc_pedit.h
 header-y += tc_skbedit.h
 header-y += tc_vlan.h
+header-y += tc_bpf.h
diff --git a/include/uapi/linux/tc_act/tc_bpf.h b/include/uapi/linux/tc_act/tc_bpf.h
new file mode 100644 (file)
index 0000000..5288bd7
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015 Jiri Pirko <jiri@resnulli.us>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_TC_BPF_H
+#define __LINUX_TC_BPF_H
+
+#include <linux/pkt_cls.h>
+
+#define TCA_ACT_BPF 13
+
+struct tc_act_bpf {
+       tc_gen;
+};
+
+enum {
+       TCA_ACT_BPF_UNSPEC,
+       TCA_ACT_BPF_TM,
+       TCA_ACT_BPF_PARMS,
+       TCA_ACT_BPF_OPS_LEN,
+       TCA_ACT_BPF_OPS,
+       __TCA_ACT_BPF_MAX,
+};
+#define TCA_ACT_BPF_MAX (__TCA_ACT_BPF_MAX - 1)
+
+#endif
index c54c9d9d1ffb814e7f4ead7e1db3dbf6c4a9adb7..466943551581956b7c4b349c9dd8d5bbf0a080df 100644 (file)
@@ -698,6 +698,18 @@ config NET_ACT_VLAN
          To compile this code as a module, choose M here: the
          module will be called act_vlan.
 
+config NET_ACT_BPF
+        tristate "BPF based action"
+        depends on NET_CLS_ACT
+        ---help---
+         Say Y here to execute BPF code on packets. The BPF code will decide
+         if the packet should be dropped or not.
+
+         If unsure, say N.
+
+         To compile this code as a module, choose M here: the
+         module will be called act_bpf.
+
 config NET_CLS_IND
        bool "Incoming device classification"
        depends on NET_CLS_U32 || NET_CLS_FW
index 679f24ae7f933d59298be54ca96b4728b9f7280b..7ca2b4e76312674fc608dd5113585bb594f8ab45 100644 (file)
@@ -17,6 +17,7 @@ obj-$(CONFIG_NET_ACT_SIMP)    += act_simple.o
 obj-$(CONFIG_NET_ACT_SKBEDIT)  += act_skbedit.o
 obj-$(CONFIG_NET_ACT_CSUM)     += act_csum.o
 obj-$(CONFIG_NET_ACT_VLAN)     += act_vlan.o
+obj-$(CONFIG_NET_ACT_BPF)      += act_bpf.o
 obj-$(CONFIG_NET_SCH_FIFO)     += sch_fifo.o
 obj-$(CONFIG_NET_SCH_CBQ)      += sch_cbq.o
 obj-$(CONFIG_NET_SCH_HTB)      += sch_htb.o
diff --git a/net/sched/act_bpf.c b/net/sched/act_bpf.c
new file mode 100644 (file)
index 0000000..1bd257e
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2015 Jiri Pirko <jiri@resnulli.us>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/filter.h>
+#include <net/netlink.h>
+#include <net/pkt_sched.h>
+
+#include <linux/tc_act/tc_bpf.h>
+#include <net/tc_act/tc_bpf.h>
+
+#define BPF_TAB_MASK     15
+
+static int tcf_bpf(struct sk_buff *skb, const struct tc_action *a,
+                  struct tcf_result *res)
+{
+       struct tcf_bpf *b = a->priv;
+       int action;
+       int filter_res;
+
+       spin_lock(&b->tcf_lock);
+       b->tcf_tm.lastuse = jiffies;
+       bstats_update(&b->tcf_bstats, skb);
+       action = b->tcf_action;
+
+       filter_res = BPF_PROG_RUN(b->filter, skb);
+       if (filter_res == 0) {
+               /* Return code 0 from the BPF program
+                * is being interpreted as a drop here.
+                */
+               action = TC_ACT_SHOT;
+               b->tcf_qstats.drops++;
+       }
+
+       spin_unlock(&b->tcf_lock);
+       return action;
+}
+
+static int tcf_bpf_dump(struct sk_buff *skb, struct tc_action *a,
+                       int bind, int ref)
+{
+       unsigned char *tp = skb_tail_pointer(skb);
+       struct tcf_bpf *b = a->priv;
+       struct tc_act_bpf opt = {
+               .index    = b->tcf_index,
+               .refcnt   = b->tcf_refcnt - ref,
+               .bindcnt  = b->tcf_bindcnt - bind,
+               .action   = b->tcf_action,
+       };
+       struct tcf_t t;
+       struct nlattr *nla;
+
+       if (nla_put(skb, TCA_ACT_BPF_PARMS, sizeof(opt), &opt))
+               goto nla_put_failure;
+
+       if (nla_put_u16(skb, TCA_ACT_BPF_OPS_LEN, b->bpf_num_ops))
+               goto nla_put_failure;
+
+       nla = nla_reserve(skb, TCA_ACT_BPF_OPS, b->bpf_num_ops *
+                         sizeof(struct sock_filter));
+       if (!nla)
+               goto nla_put_failure;
+
+       memcpy(nla_data(nla), b->bpf_ops, nla_len(nla));
+
+       t.install = jiffies_to_clock_t(jiffies - b->tcf_tm.install);
+       t.lastuse = jiffies_to_clock_t(jiffies - b->tcf_tm.lastuse);
+       t.expires = jiffies_to_clock_t(b->tcf_tm.expires);
+       if (nla_put(skb, TCA_ACT_BPF_TM, sizeof(t), &t))
+               goto nla_put_failure;
+       return skb->len;
+
+nla_put_failure:
+       nlmsg_trim(skb, tp);
+       return -1;
+}
+
+static const struct nla_policy act_bpf_policy[TCA_ACT_BPF_MAX + 1] = {
+       [TCA_ACT_BPF_PARMS]     = { .len = sizeof(struct tc_act_bpf) },
+       [TCA_ACT_BPF_OPS_LEN]   = { .type = NLA_U16 },
+       [TCA_ACT_BPF_OPS]       = { .type = NLA_BINARY,
+                                   .len = sizeof(struct sock_filter) * BPF_MAXINSNS },
+};
+
+static int tcf_bpf_init(struct net *net, struct nlattr *nla,
+                       struct nlattr *est, struct tc_action *a,
+                       int ovr, int bind)
+{
+       struct nlattr *tb[TCA_ACT_BPF_MAX + 1];
+       struct tc_act_bpf *parm;
+       struct tcf_bpf *b;
+       u16 bpf_size, bpf_num_ops;
+       struct sock_filter *bpf_ops;
+       struct sock_fprog_kern tmp;
+       struct bpf_prog *fp;
+       int ret;
+
+       if (!nla)
+               return -EINVAL;
+
+       ret = nla_parse_nested(tb, TCA_ACT_BPF_MAX, nla, act_bpf_policy);
+       if (ret < 0)
+               return ret;
+
+       if (!tb[TCA_ACT_BPF_PARMS] ||
+           !tb[TCA_ACT_BPF_OPS_LEN] || !tb[TCA_ACT_BPF_OPS])
+               return -EINVAL;
+       parm = nla_data(tb[TCA_ACT_BPF_PARMS]);
+
+       bpf_num_ops = nla_get_u16(tb[TCA_ACT_BPF_OPS_LEN]);
+       if (bpf_num_ops > BPF_MAXINSNS || bpf_num_ops == 0)
+               return -EINVAL;
+
+       bpf_size = bpf_num_ops * sizeof(*bpf_ops);
+       bpf_ops = kzalloc(bpf_size, GFP_KERNEL);
+       if (!bpf_ops)
+               return -ENOMEM;
+
+       memcpy(bpf_ops, nla_data(tb[TCA_ACT_BPF_OPS]), bpf_size);
+
+       tmp.len = bpf_num_ops;
+       tmp.filter = bpf_ops;
+
+       ret = bpf_prog_create(&fp, &tmp);
+       if (ret)
+               goto free_bpf_ops;
+
+       if (!tcf_hash_check(parm->index, a, bind)) {
+               ret = tcf_hash_create(parm->index, est, a, sizeof(*b), bind);
+               if (ret)
+                       goto destroy_fp;
+
+               ret = ACT_P_CREATED;
+       } else {
+               if (bind)
+                       goto destroy_fp;
+               tcf_hash_release(a, bind);
+               if (!ovr) {
+                       ret = -EEXIST;
+                       goto destroy_fp;
+               }
+       }
+
+       b = to_bpf(a);
+       spin_lock_bh(&b->tcf_lock);
+       b->tcf_action = parm->action;
+       b->bpf_num_ops = bpf_num_ops;
+       b->bpf_ops = bpf_ops;
+       b->filter = fp;
+       spin_unlock_bh(&b->tcf_lock);
+
+       if (ret == ACT_P_CREATED)
+               tcf_hash_insert(a);
+       return ret;
+
+destroy_fp:
+       bpf_prog_destroy(fp);
+free_bpf_ops:
+       kfree(bpf_ops);
+       return ret;
+}
+
+static void tcf_bpf_cleanup(struct tc_action *a, int bind)
+{
+       struct tcf_bpf *b = a->priv;
+
+       bpf_prog_destroy(b->filter);
+}
+
+static struct tc_action_ops act_bpf_ops = {
+       .kind =         "bpf",
+       .type =         TCA_ACT_BPF,
+       .owner =        THIS_MODULE,
+       .act =          tcf_bpf,
+       .dump =         tcf_bpf_dump,
+       .cleanup =      tcf_bpf_cleanup,
+       .init =         tcf_bpf_init,
+};
+
+static int __init bpf_init_module(void)
+{
+       return tcf_register_action(&act_bpf_ops, BPF_TAB_MASK);
+}
+
+static void __exit bpf_cleanup_module(void)
+{
+       tcf_unregister_action(&act_bpf_ops);
+}
+
+module_init(bpf_init_module);
+module_exit(bpf_cleanup_module);
+
+MODULE_AUTHOR("Jiri Pirko <jiri@resnulli.us>");
+MODULE_DESCRIPTION("TC BPF based action");
+MODULE_LICENSE("GPL v2");