162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Berkeley Packet Filter based traffic classifier
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Might be used to classify traffic through flexible, user-defined and
662306a36Sopenharmony_ci * possibly JIT-ed BPF filters for traffic control as an alternative to
762306a36Sopenharmony_ci * ematches.
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * (C) 2013 Daniel Borkmann <dborkman@redhat.com>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/types.h>
1462306a36Sopenharmony_ci#include <linux/skbuff.h>
1562306a36Sopenharmony_ci#include <linux/filter.h>
1662306a36Sopenharmony_ci#include <linux/bpf.h>
1762306a36Sopenharmony_ci#include <linux/idr.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <net/rtnetlink.h>
2062306a36Sopenharmony_ci#include <net/pkt_cls.h>
2162306a36Sopenharmony_ci#include <net/sock.h>
2262306a36Sopenharmony_ci#include <net/tc_wrapper.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2562306a36Sopenharmony_ciMODULE_AUTHOR("Daniel Borkmann <dborkman@redhat.com>");
2662306a36Sopenharmony_ciMODULE_DESCRIPTION("TC BPF based classifier");
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define CLS_BPF_NAME_LEN	256
2962306a36Sopenharmony_ci#define CLS_BPF_SUPPORTED_GEN_FLAGS		\
3062306a36Sopenharmony_ci	(TCA_CLS_FLAGS_SKIP_HW | TCA_CLS_FLAGS_SKIP_SW)
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistruct cls_bpf_head {
3362306a36Sopenharmony_ci	struct list_head plist;
3462306a36Sopenharmony_ci	struct idr handle_idr;
3562306a36Sopenharmony_ci	struct rcu_head rcu;
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistruct cls_bpf_prog {
3962306a36Sopenharmony_ci	struct bpf_prog *filter;
4062306a36Sopenharmony_ci	struct list_head link;
4162306a36Sopenharmony_ci	struct tcf_result res;
4262306a36Sopenharmony_ci	bool exts_integrated;
4362306a36Sopenharmony_ci	u32 gen_flags;
4462306a36Sopenharmony_ci	unsigned int in_hw_count;
4562306a36Sopenharmony_ci	struct tcf_exts exts;
4662306a36Sopenharmony_ci	u32 handle;
4762306a36Sopenharmony_ci	u16 bpf_num_ops;
4862306a36Sopenharmony_ci	struct sock_filter *bpf_ops;
4962306a36Sopenharmony_ci	const char *bpf_name;
5062306a36Sopenharmony_ci	struct tcf_proto *tp;
5162306a36Sopenharmony_ci	struct rcu_work rwork;
5262306a36Sopenharmony_ci};
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic const struct nla_policy bpf_policy[TCA_BPF_MAX + 1] = {
5562306a36Sopenharmony_ci	[TCA_BPF_CLASSID]	= { .type = NLA_U32 },
5662306a36Sopenharmony_ci	[TCA_BPF_FLAGS]		= { .type = NLA_U32 },
5762306a36Sopenharmony_ci	[TCA_BPF_FLAGS_GEN]	= { .type = NLA_U32 },
5862306a36Sopenharmony_ci	[TCA_BPF_FD]		= { .type = NLA_U32 },
5962306a36Sopenharmony_ci	[TCA_BPF_NAME]		= { .type = NLA_NUL_STRING,
6062306a36Sopenharmony_ci				    .len = CLS_BPF_NAME_LEN },
6162306a36Sopenharmony_ci	[TCA_BPF_OPS_LEN]	= { .type = NLA_U16 },
6262306a36Sopenharmony_ci	[TCA_BPF_OPS]		= { .type = NLA_BINARY,
6362306a36Sopenharmony_ci				    .len = sizeof(struct sock_filter) * BPF_MAXINSNS },
6462306a36Sopenharmony_ci};
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic int cls_bpf_exec_opcode(int code)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	switch (code) {
6962306a36Sopenharmony_ci	case TC_ACT_OK:
7062306a36Sopenharmony_ci	case TC_ACT_SHOT:
7162306a36Sopenharmony_ci	case TC_ACT_STOLEN:
7262306a36Sopenharmony_ci	case TC_ACT_TRAP:
7362306a36Sopenharmony_ci	case TC_ACT_REDIRECT:
7462306a36Sopenharmony_ci	case TC_ACT_UNSPEC:
7562306a36Sopenharmony_ci		return code;
7662306a36Sopenharmony_ci	default:
7762306a36Sopenharmony_ci		return TC_ACT_UNSPEC;
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ciTC_INDIRECT_SCOPE int cls_bpf_classify(struct sk_buff *skb,
8262306a36Sopenharmony_ci				       const struct tcf_proto *tp,
8362306a36Sopenharmony_ci				       struct tcf_result *res)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	struct cls_bpf_head *head = rcu_dereference_bh(tp->root);
8662306a36Sopenharmony_ci	bool at_ingress = skb_at_tc_ingress(skb);
8762306a36Sopenharmony_ci	struct cls_bpf_prog *prog;
8862306a36Sopenharmony_ci	int ret = -1;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	list_for_each_entry_rcu(prog, &head->plist, link) {
9162306a36Sopenharmony_ci		int filter_res;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci		qdisc_skb_cb(skb)->tc_classid = prog->res.classid;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci		if (tc_skip_sw(prog->gen_flags)) {
9662306a36Sopenharmony_ci			filter_res = prog->exts_integrated ? TC_ACT_UNSPEC : 0;
9762306a36Sopenharmony_ci		} else if (at_ingress) {
9862306a36Sopenharmony_ci			/* It is safe to push/pull even if skb_shared() */
9962306a36Sopenharmony_ci			__skb_push(skb, skb->mac_len);
10062306a36Sopenharmony_ci			bpf_compute_data_pointers(skb);
10162306a36Sopenharmony_ci			filter_res = bpf_prog_run(prog->filter, skb);
10262306a36Sopenharmony_ci			__skb_pull(skb, skb->mac_len);
10362306a36Sopenharmony_ci		} else {
10462306a36Sopenharmony_ci			bpf_compute_data_pointers(skb);
10562306a36Sopenharmony_ci			filter_res = bpf_prog_run(prog->filter, skb);
10662306a36Sopenharmony_ci		}
10762306a36Sopenharmony_ci		if (unlikely(!skb->tstamp && skb->mono_delivery_time))
10862306a36Sopenharmony_ci			skb->mono_delivery_time = 0;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci		if (prog->exts_integrated) {
11162306a36Sopenharmony_ci			res->class   = 0;
11262306a36Sopenharmony_ci			res->classid = TC_H_MAJ(prog->res.classid) |
11362306a36Sopenharmony_ci				       qdisc_skb_cb(skb)->tc_classid;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci			ret = cls_bpf_exec_opcode(filter_res);
11662306a36Sopenharmony_ci			if (ret == TC_ACT_UNSPEC)
11762306a36Sopenharmony_ci				continue;
11862306a36Sopenharmony_ci			break;
11962306a36Sopenharmony_ci		}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci		if (filter_res == 0)
12262306a36Sopenharmony_ci			continue;
12362306a36Sopenharmony_ci		if (filter_res != -1) {
12462306a36Sopenharmony_ci			res->class   = 0;
12562306a36Sopenharmony_ci			res->classid = filter_res;
12662306a36Sopenharmony_ci		} else {
12762306a36Sopenharmony_ci			*res = prog->res;
12862306a36Sopenharmony_ci		}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci		ret = tcf_exts_exec(skb, &prog->exts, res);
13162306a36Sopenharmony_ci		if (ret < 0)
13262306a36Sopenharmony_ci			continue;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci		break;
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	return ret;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic bool cls_bpf_is_ebpf(const struct cls_bpf_prog *prog)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	return !prog->bpf_ops;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic int cls_bpf_offload_cmd(struct tcf_proto *tp, struct cls_bpf_prog *prog,
14662306a36Sopenharmony_ci			       struct cls_bpf_prog *oldprog,
14762306a36Sopenharmony_ci			       struct netlink_ext_ack *extack)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	struct tcf_block *block = tp->chain->block;
15062306a36Sopenharmony_ci	struct tc_cls_bpf_offload cls_bpf = {};
15162306a36Sopenharmony_ci	struct cls_bpf_prog *obj;
15262306a36Sopenharmony_ci	bool skip_sw;
15362306a36Sopenharmony_ci	int err;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	skip_sw = prog && tc_skip_sw(prog->gen_flags);
15662306a36Sopenharmony_ci	obj = prog ?: oldprog;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	tc_cls_common_offload_init(&cls_bpf.common, tp, obj->gen_flags, extack);
15962306a36Sopenharmony_ci	cls_bpf.command = TC_CLSBPF_OFFLOAD;
16062306a36Sopenharmony_ci	cls_bpf.exts = &obj->exts;
16162306a36Sopenharmony_ci	cls_bpf.prog = prog ? prog->filter : NULL;
16262306a36Sopenharmony_ci	cls_bpf.oldprog = oldprog ? oldprog->filter : NULL;
16362306a36Sopenharmony_ci	cls_bpf.name = obj->bpf_name;
16462306a36Sopenharmony_ci	cls_bpf.exts_integrated = obj->exts_integrated;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (oldprog && prog)
16762306a36Sopenharmony_ci		err = tc_setup_cb_replace(block, tp, TC_SETUP_CLSBPF, &cls_bpf,
16862306a36Sopenharmony_ci					  skip_sw, &oldprog->gen_flags,
16962306a36Sopenharmony_ci					  &oldprog->in_hw_count,
17062306a36Sopenharmony_ci					  &prog->gen_flags, &prog->in_hw_count,
17162306a36Sopenharmony_ci					  true);
17262306a36Sopenharmony_ci	else if (prog)
17362306a36Sopenharmony_ci		err = tc_setup_cb_add(block, tp, TC_SETUP_CLSBPF, &cls_bpf,
17462306a36Sopenharmony_ci				      skip_sw, &prog->gen_flags,
17562306a36Sopenharmony_ci				      &prog->in_hw_count, true);
17662306a36Sopenharmony_ci	else
17762306a36Sopenharmony_ci		err = tc_setup_cb_destroy(block, tp, TC_SETUP_CLSBPF, &cls_bpf,
17862306a36Sopenharmony_ci					  skip_sw, &oldprog->gen_flags,
17962306a36Sopenharmony_ci					  &oldprog->in_hw_count, true);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (prog && err) {
18262306a36Sopenharmony_ci		cls_bpf_offload_cmd(tp, oldprog, prog, extack);
18362306a36Sopenharmony_ci		return err;
18462306a36Sopenharmony_ci	}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (prog && skip_sw && !(prog->gen_flags & TCA_CLS_FLAGS_IN_HW))
18762306a36Sopenharmony_ci		return -EINVAL;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return 0;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic u32 cls_bpf_flags(u32 flags)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	return flags & CLS_BPF_SUPPORTED_GEN_FLAGS;
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic int cls_bpf_offload(struct tcf_proto *tp, struct cls_bpf_prog *prog,
19862306a36Sopenharmony_ci			   struct cls_bpf_prog *oldprog,
19962306a36Sopenharmony_ci			   struct netlink_ext_ack *extack)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	if (prog && oldprog &&
20262306a36Sopenharmony_ci	    cls_bpf_flags(prog->gen_flags) !=
20362306a36Sopenharmony_ci	    cls_bpf_flags(oldprog->gen_flags))
20462306a36Sopenharmony_ci		return -EINVAL;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	if (prog && tc_skip_hw(prog->gen_flags))
20762306a36Sopenharmony_ci		prog = NULL;
20862306a36Sopenharmony_ci	if (oldprog && tc_skip_hw(oldprog->gen_flags))
20962306a36Sopenharmony_ci		oldprog = NULL;
21062306a36Sopenharmony_ci	if (!prog && !oldprog)
21162306a36Sopenharmony_ci		return 0;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	return cls_bpf_offload_cmd(tp, prog, oldprog, extack);
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic void cls_bpf_stop_offload(struct tcf_proto *tp,
21762306a36Sopenharmony_ci				 struct cls_bpf_prog *prog,
21862306a36Sopenharmony_ci				 struct netlink_ext_ack *extack)
21962306a36Sopenharmony_ci{
22062306a36Sopenharmony_ci	int err;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	err = cls_bpf_offload_cmd(tp, NULL, prog, extack);
22362306a36Sopenharmony_ci	if (err)
22462306a36Sopenharmony_ci		pr_err("Stopping hardware offload failed: %d\n", err);
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_cistatic void cls_bpf_offload_update_stats(struct tcf_proto *tp,
22862306a36Sopenharmony_ci					 struct cls_bpf_prog *prog)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	struct tcf_block *block = tp->chain->block;
23162306a36Sopenharmony_ci	struct tc_cls_bpf_offload cls_bpf = {};
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	tc_cls_common_offload_init(&cls_bpf.common, tp, prog->gen_flags, NULL);
23462306a36Sopenharmony_ci	cls_bpf.command = TC_CLSBPF_STATS;
23562306a36Sopenharmony_ci	cls_bpf.exts = &prog->exts;
23662306a36Sopenharmony_ci	cls_bpf.prog = prog->filter;
23762306a36Sopenharmony_ci	cls_bpf.name = prog->bpf_name;
23862306a36Sopenharmony_ci	cls_bpf.exts_integrated = prog->exts_integrated;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	tc_setup_cb_call(block, TC_SETUP_CLSBPF, &cls_bpf, false, true);
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic int cls_bpf_init(struct tcf_proto *tp)
24462306a36Sopenharmony_ci{
24562306a36Sopenharmony_ci	struct cls_bpf_head *head;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	head = kzalloc(sizeof(*head), GFP_KERNEL);
24862306a36Sopenharmony_ci	if (head == NULL)
24962306a36Sopenharmony_ci		return -ENOBUFS;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	INIT_LIST_HEAD_RCU(&head->plist);
25262306a36Sopenharmony_ci	idr_init(&head->handle_idr);
25362306a36Sopenharmony_ci	rcu_assign_pointer(tp->root, head);
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	return 0;
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_cistatic void cls_bpf_free_parms(struct cls_bpf_prog *prog)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	if (cls_bpf_is_ebpf(prog))
26162306a36Sopenharmony_ci		bpf_prog_put(prog->filter);
26262306a36Sopenharmony_ci	else
26362306a36Sopenharmony_ci		bpf_prog_destroy(prog->filter);
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	kfree(prog->bpf_name);
26662306a36Sopenharmony_ci	kfree(prog->bpf_ops);
26762306a36Sopenharmony_ci}
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_cistatic void __cls_bpf_delete_prog(struct cls_bpf_prog *prog)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	tcf_exts_destroy(&prog->exts);
27262306a36Sopenharmony_ci	tcf_exts_put_net(&prog->exts);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	cls_bpf_free_parms(prog);
27562306a36Sopenharmony_ci	kfree(prog);
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_cistatic void cls_bpf_delete_prog_work(struct work_struct *work)
27962306a36Sopenharmony_ci{
28062306a36Sopenharmony_ci	struct cls_bpf_prog *prog = container_of(to_rcu_work(work),
28162306a36Sopenharmony_ci						 struct cls_bpf_prog,
28262306a36Sopenharmony_ci						 rwork);
28362306a36Sopenharmony_ci	rtnl_lock();
28462306a36Sopenharmony_ci	__cls_bpf_delete_prog(prog);
28562306a36Sopenharmony_ci	rtnl_unlock();
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic void __cls_bpf_delete(struct tcf_proto *tp, struct cls_bpf_prog *prog,
28962306a36Sopenharmony_ci			     struct netlink_ext_ack *extack)
29062306a36Sopenharmony_ci{
29162306a36Sopenharmony_ci	struct cls_bpf_head *head = rtnl_dereference(tp->root);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	idr_remove(&head->handle_idr, prog->handle);
29462306a36Sopenharmony_ci	cls_bpf_stop_offload(tp, prog, extack);
29562306a36Sopenharmony_ci	list_del_rcu(&prog->link);
29662306a36Sopenharmony_ci	tcf_unbind_filter(tp, &prog->res);
29762306a36Sopenharmony_ci	if (tcf_exts_get_net(&prog->exts))
29862306a36Sopenharmony_ci		tcf_queue_work(&prog->rwork, cls_bpf_delete_prog_work);
29962306a36Sopenharmony_ci	else
30062306a36Sopenharmony_ci		__cls_bpf_delete_prog(prog);
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic int cls_bpf_delete(struct tcf_proto *tp, void *arg, bool *last,
30462306a36Sopenharmony_ci			  bool rtnl_held, struct netlink_ext_ack *extack)
30562306a36Sopenharmony_ci{
30662306a36Sopenharmony_ci	struct cls_bpf_head *head = rtnl_dereference(tp->root);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	__cls_bpf_delete(tp, arg, extack);
30962306a36Sopenharmony_ci	*last = list_empty(&head->plist);
31062306a36Sopenharmony_ci	return 0;
31162306a36Sopenharmony_ci}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_cistatic void cls_bpf_destroy(struct tcf_proto *tp, bool rtnl_held,
31462306a36Sopenharmony_ci			    struct netlink_ext_ack *extack)
31562306a36Sopenharmony_ci{
31662306a36Sopenharmony_ci	struct cls_bpf_head *head = rtnl_dereference(tp->root);
31762306a36Sopenharmony_ci	struct cls_bpf_prog *prog, *tmp;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	list_for_each_entry_safe(prog, tmp, &head->plist, link)
32062306a36Sopenharmony_ci		__cls_bpf_delete(tp, prog, extack);
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	idr_destroy(&head->handle_idr);
32362306a36Sopenharmony_ci	kfree_rcu(head, rcu);
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cistatic void *cls_bpf_get(struct tcf_proto *tp, u32 handle)
32762306a36Sopenharmony_ci{
32862306a36Sopenharmony_ci	struct cls_bpf_head *head = rtnl_dereference(tp->root);
32962306a36Sopenharmony_ci	struct cls_bpf_prog *prog;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	list_for_each_entry(prog, &head->plist, link) {
33262306a36Sopenharmony_ci		if (prog->handle == handle)
33362306a36Sopenharmony_ci			return prog;
33462306a36Sopenharmony_ci	}
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	return NULL;
33762306a36Sopenharmony_ci}
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_cistatic int cls_bpf_prog_from_ops(struct nlattr **tb, struct cls_bpf_prog *prog)
34062306a36Sopenharmony_ci{
34162306a36Sopenharmony_ci	struct sock_filter *bpf_ops;
34262306a36Sopenharmony_ci	struct sock_fprog_kern fprog_tmp;
34362306a36Sopenharmony_ci	struct bpf_prog *fp;
34462306a36Sopenharmony_ci	u16 bpf_size, bpf_num_ops;
34562306a36Sopenharmony_ci	int ret;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	bpf_num_ops = nla_get_u16(tb[TCA_BPF_OPS_LEN]);
34862306a36Sopenharmony_ci	if (bpf_num_ops > BPF_MAXINSNS || bpf_num_ops == 0)
34962306a36Sopenharmony_ci		return -EINVAL;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	bpf_size = bpf_num_ops * sizeof(*bpf_ops);
35262306a36Sopenharmony_ci	if (bpf_size != nla_len(tb[TCA_BPF_OPS]))
35362306a36Sopenharmony_ci		return -EINVAL;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	bpf_ops = kmemdup(nla_data(tb[TCA_BPF_OPS]), bpf_size, GFP_KERNEL);
35662306a36Sopenharmony_ci	if (bpf_ops == NULL)
35762306a36Sopenharmony_ci		return -ENOMEM;
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	fprog_tmp.len = bpf_num_ops;
36062306a36Sopenharmony_ci	fprog_tmp.filter = bpf_ops;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	ret = bpf_prog_create(&fp, &fprog_tmp);
36362306a36Sopenharmony_ci	if (ret < 0) {
36462306a36Sopenharmony_ci		kfree(bpf_ops);
36562306a36Sopenharmony_ci		return ret;
36662306a36Sopenharmony_ci	}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	prog->bpf_ops = bpf_ops;
36962306a36Sopenharmony_ci	prog->bpf_num_ops = bpf_num_ops;
37062306a36Sopenharmony_ci	prog->bpf_name = NULL;
37162306a36Sopenharmony_ci	prog->filter = fp;
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	return 0;
37462306a36Sopenharmony_ci}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_cistatic int cls_bpf_prog_from_efd(struct nlattr **tb, struct cls_bpf_prog *prog,
37762306a36Sopenharmony_ci				 u32 gen_flags, const struct tcf_proto *tp)
37862306a36Sopenharmony_ci{
37962306a36Sopenharmony_ci	struct bpf_prog *fp;
38062306a36Sopenharmony_ci	char *name = NULL;
38162306a36Sopenharmony_ci	bool skip_sw;
38262306a36Sopenharmony_ci	u32 bpf_fd;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	bpf_fd = nla_get_u32(tb[TCA_BPF_FD]);
38562306a36Sopenharmony_ci	skip_sw = gen_flags & TCA_CLS_FLAGS_SKIP_SW;
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	fp = bpf_prog_get_type_dev(bpf_fd, BPF_PROG_TYPE_SCHED_CLS, skip_sw);
38862306a36Sopenharmony_ci	if (IS_ERR(fp))
38962306a36Sopenharmony_ci		return PTR_ERR(fp);
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	if (tb[TCA_BPF_NAME]) {
39262306a36Sopenharmony_ci		name = nla_memdup(tb[TCA_BPF_NAME], GFP_KERNEL);
39362306a36Sopenharmony_ci		if (!name) {
39462306a36Sopenharmony_ci			bpf_prog_put(fp);
39562306a36Sopenharmony_ci			return -ENOMEM;
39662306a36Sopenharmony_ci		}
39762306a36Sopenharmony_ci	}
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	prog->bpf_ops = NULL;
40062306a36Sopenharmony_ci	prog->bpf_name = name;
40162306a36Sopenharmony_ci	prog->filter = fp;
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci	if (fp->dst_needed)
40462306a36Sopenharmony_ci		tcf_block_netif_keep_dst(tp->chain->block);
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	return 0;
40762306a36Sopenharmony_ci}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_cistatic int cls_bpf_change(struct net *net, struct sk_buff *in_skb,
41062306a36Sopenharmony_ci			  struct tcf_proto *tp, unsigned long base,
41162306a36Sopenharmony_ci			  u32 handle, struct nlattr **tca,
41262306a36Sopenharmony_ci			  void **arg, u32 flags,
41362306a36Sopenharmony_ci			  struct netlink_ext_ack *extack)
41462306a36Sopenharmony_ci{
41562306a36Sopenharmony_ci	struct cls_bpf_head *head = rtnl_dereference(tp->root);
41662306a36Sopenharmony_ci	bool is_bpf, is_ebpf, have_exts = false;
41762306a36Sopenharmony_ci	struct cls_bpf_prog *oldprog = *arg;
41862306a36Sopenharmony_ci	struct nlattr *tb[TCA_BPF_MAX + 1];
41962306a36Sopenharmony_ci	bool bound_to_filter = false;
42062306a36Sopenharmony_ci	struct cls_bpf_prog *prog;
42162306a36Sopenharmony_ci	u32 gen_flags = 0;
42262306a36Sopenharmony_ci	int ret;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	if (tca[TCA_OPTIONS] == NULL)
42562306a36Sopenharmony_ci		return -EINVAL;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	ret = nla_parse_nested_deprecated(tb, TCA_BPF_MAX, tca[TCA_OPTIONS],
42862306a36Sopenharmony_ci					  bpf_policy, NULL);
42962306a36Sopenharmony_ci	if (ret < 0)
43062306a36Sopenharmony_ci		return ret;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	prog = kzalloc(sizeof(*prog), GFP_KERNEL);
43362306a36Sopenharmony_ci	if (!prog)
43462306a36Sopenharmony_ci		return -ENOBUFS;
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	ret = tcf_exts_init(&prog->exts, net, TCA_BPF_ACT, TCA_BPF_POLICE);
43762306a36Sopenharmony_ci	if (ret < 0)
43862306a36Sopenharmony_ci		goto errout;
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	if (oldprog) {
44162306a36Sopenharmony_ci		if (handle && oldprog->handle != handle) {
44262306a36Sopenharmony_ci			ret = -EINVAL;
44362306a36Sopenharmony_ci			goto errout;
44462306a36Sopenharmony_ci		}
44562306a36Sopenharmony_ci	}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	if (handle == 0) {
44862306a36Sopenharmony_ci		handle = 1;
44962306a36Sopenharmony_ci		ret = idr_alloc_u32(&head->handle_idr, prog, &handle,
45062306a36Sopenharmony_ci				    INT_MAX, GFP_KERNEL);
45162306a36Sopenharmony_ci	} else if (!oldprog) {
45262306a36Sopenharmony_ci		ret = idr_alloc_u32(&head->handle_idr, prog, &handle,
45362306a36Sopenharmony_ci				    handle, GFP_KERNEL);
45462306a36Sopenharmony_ci	}
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	if (ret)
45762306a36Sopenharmony_ci		goto errout;
45862306a36Sopenharmony_ci	prog->handle = handle;
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	is_bpf = tb[TCA_BPF_OPS_LEN] && tb[TCA_BPF_OPS];
46162306a36Sopenharmony_ci	is_ebpf = tb[TCA_BPF_FD];
46262306a36Sopenharmony_ci	if ((!is_bpf && !is_ebpf) || (is_bpf && is_ebpf)) {
46362306a36Sopenharmony_ci		ret = -EINVAL;
46462306a36Sopenharmony_ci		goto errout_idr;
46562306a36Sopenharmony_ci	}
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	ret = tcf_exts_validate(net, tp, tb, tca[TCA_RATE], &prog->exts,
46862306a36Sopenharmony_ci				flags, extack);
46962306a36Sopenharmony_ci	if (ret < 0)
47062306a36Sopenharmony_ci		goto errout_idr;
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	if (tb[TCA_BPF_FLAGS]) {
47362306a36Sopenharmony_ci		u32 bpf_flags = nla_get_u32(tb[TCA_BPF_FLAGS]);
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci		if (bpf_flags & ~TCA_BPF_FLAG_ACT_DIRECT) {
47662306a36Sopenharmony_ci			ret = -EINVAL;
47762306a36Sopenharmony_ci			goto errout_idr;
47862306a36Sopenharmony_ci		}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci		have_exts = bpf_flags & TCA_BPF_FLAG_ACT_DIRECT;
48162306a36Sopenharmony_ci	}
48262306a36Sopenharmony_ci	if (tb[TCA_BPF_FLAGS_GEN]) {
48362306a36Sopenharmony_ci		gen_flags = nla_get_u32(tb[TCA_BPF_FLAGS_GEN]);
48462306a36Sopenharmony_ci		if (gen_flags & ~CLS_BPF_SUPPORTED_GEN_FLAGS ||
48562306a36Sopenharmony_ci		    !tc_flags_valid(gen_flags)) {
48662306a36Sopenharmony_ci			ret = -EINVAL;
48762306a36Sopenharmony_ci			goto errout_idr;
48862306a36Sopenharmony_ci		}
48962306a36Sopenharmony_ci	}
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	prog->exts_integrated = have_exts;
49262306a36Sopenharmony_ci	prog->gen_flags = gen_flags;
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	ret = is_bpf ? cls_bpf_prog_from_ops(tb, prog) :
49562306a36Sopenharmony_ci		cls_bpf_prog_from_efd(tb, prog, gen_flags, tp);
49662306a36Sopenharmony_ci	if (ret < 0)
49762306a36Sopenharmony_ci		goto errout_idr;
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	if (tb[TCA_BPF_CLASSID]) {
50062306a36Sopenharmony_ci		prog->res.classid = nla_get_u32(tb[TCA_BPF_CLASSID]);
50162306a36Sopenharmony_ci		tcf_bind_filter(tp, &prog->res, base);
50262306a36Sopenharmony_ci		bound_to_filter = true;
50362306a36Sopenharmony_ci	}
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	ret = cls_bpf_offload(tp, prog, oldprog, extack);
50662306a36Sopenharmony_ci	if (ret)
50762306a36Sopenharmony_ci		goto errout_parms;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	if (!tc_in_hw(prog->gen_flags))
51062306a36Sopenharmony_ci		prog->gen_flags |= TCA_CLS_FLAGS_NOT_IN_HW;
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	if (oldprog) {
51362306a36Sopenharmony_ci		idr_replace(&head->handle_idr, prog, handle);
51462306a36Sopenharmony_ci		list_replace_rcu(&oldprog->link, &prog->link);
51562306a36Sopenharmony_ci		tcf_unbind_filter(tp, &oldprog->res);
51662306a36Sopenharmony_ci		tcf_exts_get_net(&oldprog->exts);
51762306a36Sopenharmony_ci		tcf_queue_work(&oldprog->rwork, cls_bpf_delete_prog_work);
51862306a36Sopenharmony_ci	} else {
51962306a36Sopenharmony_ci		list_add_rcu(&prog->link, &head->plist);
52062306a36Sopenharmony_ci	}
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	*arg = prog;
52362306a36Sopenharmony_ci	return 0;
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_cierrout_parms:
52662306a36Sopenharmony_ci	if (bound_to_filter)
52762306a36Sopenharmony_ci		tcf_unbind_filter(tp, &prog->res);
52862306a36Sopenharmony_ci	cls_bpf_free_parms(prog);
52962306a36Sopenharmony_cierrout_idr:
53062306a36Sopenharmony_ci	if (!oldprog)
53162306a36Sopenharmony_ci		idr_remove(&head->handle_idr, prog->handle);
53262306a36Sopenharmony_cierrout:
53362306a36Sopenharmony_ci	tcf_exts_destroy(&prog->exts);
53462306a36Sopenharmony_ci	kfree(prog);
53562306a36Sopenharmony_ci	return ret;
53662306a36Sopenharmony_ci}
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_cistatic int cls_bpf_dump_bpf_info(const struct cls_bpf_prog *prog,
53962306a36Sopenharmony_ci				 struct sk_buff *skb)
54062306a36Sopenharmony_ci{
54162306a36Sopenharmony_ci	struct nlattr *nla;
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	if (nla_put_u16(skb, TCA_BPF_OPS_LEN, prog->bpf_num_ops))
54462306a36Sopenharmony_ci		return -EMSGSIZE;
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci	nla = nla_reserve(skb, TCA_BPF_OPS, prog->bpf_num_ops *
54762306a36Sopenharmony_ci			  sizeof(struct sock_filter));
54862306a36Sopenharmony_ci	if (nla == NULL)
54962306a36Sopenharmony_ci		return -EMSGSIZE;
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci	memcpy(nla_data(nla), prog->bpf_ops, nla_len(nla));
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	return 0;
55462306a36Sopenharmony_ci}
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_cistatic int cls_bpf_dump_ebpf_info(const struct cls_bpf_prog *prog,
55762306a36Sopenharmony_ci				  struct sk_buff *skb)
55862306a36Sopenharmony_ci{
55962306a36Sopenharmony_ci	struct nlattr *nla;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	if (prog->bpf_name &&
56262306a36Sopenharmony_ci	    nla_put_string(skb, TCA_BPF_NAME, prog->bpf_name))
56362306a36Sopenharmony_ci		return -EMSGSIZE;
56462306a36Sopenharmony_ci
56562306a36Sopenharmony_ci	if (nla_put_u32(skb, TCA_BPF_ID, prog->filter->aux->id))
56662306a36Sopenharmony_ci		return -EMSGSIZE;
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci	nla = nla_reserve(skb, TCA_BPF_TAG, sizeof(prog->filter->tag));
56962306a36Sopenharmony_ci	if (nla == NULL)
57062306a36Sopenharmony_ci		return -EMSGSIZE;
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	memcpy(nla_data(nla), prog->filter->tag, nla_len(nla));
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	return 0;
57562306a36Sopenharmony_ci}
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_cistatic int cls_bpf_dump(struct net *net, struct tcf_proto *tp, void *fh,
57862306a36Sopenharmony_ci			struct sk_buff *skb, struct tcmsg *tm, bool rtnl_held)
57962306a36Sopenharmony_ci{
58062306a36Sopenharmony_ci	struct cls_bpf_prog *prog = fh;
58162306a36Sopenharmony_ci	struct nlattr *nest;
58262306a36Sopenharmony_ci	u32 bpf_flags = 0;
58362306a36Sopenharmony_ci	int ret;
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci	if (prog == NULL)
58662306a36Sopenharmony_ci		return skb->len;
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ci	tm->tcm_handle = prog->handle;
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	cls_bpf_offload_update_stats(tp, prog);
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	nest = nla_nest_start_noflag(skb, TCA_OPTIONS);
59362306a36Sopenharmony_ci	if (nest == NULL)
59462306a36Sopenharmony_ci		goto nla_put_failure;
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci	if (prog->res.classid &&
59762306a36Sopenharmony_ci	    nla_put_u32(skb, TCA_BPF_CLASSID, prog->res.classid))
59862306a36Sopenharmony_ci		goto nla_put_failure;
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	if (cls_bpf_is_ebpf(prog))
60162306a36Sopenharmony_ci		ret = cls_bpf_dump_ebpf_info(prog, skb);
60262306a36Sopenharmony_ci	else
60362306a36Sopenharmony_ci		ret = cls_bpf_dump_bpf_info(prog, skb);
60462306a36Sopenharmony_ci	if (ret)
60562306a36Sopenharmony_ci		goto nla_put_failure;
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci	if (tcf_exts_dump(skb, &prog->exts) < 0)
60862306a36Sopenharmony_ci		goto nla_put_failure;
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	if (prog->exts_integrated)
61162306a36Sopenharmony_ci		bpf_flags |= TCA_BPF_FLAG_ACT_DIRECT;
61262306a36Sopenharmony_ci	if (bpf_flags && nla_put_u32(skb, TCA_BPF_FLAGS, bpf_flags))
61362306a36Sopenharmony_ci		goto nla_put_failure;
61462306a36Sopenharmony_ci	if (prog->gen_flags &&
61562306a36Sopenharmony_ci	    nla_put_u32(skb, TCA_BPF_FLAGS_GEN, prog->gen_flags))
61662306a36Sopenharmony_ci		goto nla_put_failure;
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci	nla_nest_end(skb, nest);
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	if (tcf_exts_dump_stats(skb, &prog->exts) < 0)
62162306a36Sopenharmony_ci		goto nla_put_failure;
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	return skb->len;
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_cinla_put_failure:
62662306a36Sopenharmony_ci	nla_nest_cancel(skb, nest);
62762306a36Sopenharmony_ci	return -1;
62862306a36Sopenharmony_ci}
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_cistatic void cls_bpf_bind_class(void *fh, u32 classid, unsigned long cl,
63162306a36Sopenharmony_ci			       void *q, unsigned long base)
63262306a36Sopenharmony_ci{
63362306a36Sopenharmony_ci	struct cls_bpf_prog *prog = fh;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	tc_cls_bind_class(classid, cl, q, &prog->res, base);
63662306a36Sopenharmony_ci}
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_cistatic void cls_bpf_walk(struct tcf_proto *tp, struct tcf_walker *arg,
63962306a36Sopenharmony_ci			 bool rtnl_held)
64062306a36Sopenharmony_ci{
64162306a36Sopenharmony_ci	struct cls_bpf_head *head = rtnl_dereference(tp->root);
64262306a36Sopenharmony_ci	struct cls_bpf_prog *prog;
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci	list_for_each_entry(prog, &head->plist, link) {
64562306a36Sopenharmony_ci		if (!tc_cls_stats_dump(tp, arg, prog))
64662306a36Sopenharmony_ci			break;
64762306a36Sopenharmony_ci	}
64862306a36Sopenharmony_ci}
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_cistatic int cls_bpf_reoffload(struct tcf_proto *tp, bool add, flow_setup_cb_t *cb,
65162306a36Sopenharmony_ci			     void *cb_priv, struct netlink_ext_ack *extack)
65262306a36Sopenharmony_ci{
65362306a36Sopenharmony_ci	struct cls_bpf_head *head = rtnl_dereference(tp->root);
65462306a36Sopenharmony_ci	struct tcf_block *block = tp->chain->block;
65562306a36Sopenharmony_ci	struct tc_cls_bpf_offload cls_bpf = {};
65662306a36Sopenharmony_ci	struct cls_bpf_prog *prog;
65762306a36Sopenharmony_ci	int err;
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci	list_for_each_entry(prog, &head->plist, link) {
66062306a36Sopenharmony_ci		if (tc_skip_hw(prog->gen_flags))
66162306a36Sopenharmony_ci			continue;
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ci		tc_cls_common_offload_init(&cls_bpf.common, tp, prog->gen_flags,
66462306a36Sopenharmony_ci					   extack);
66562306a36Sopenharmony_ci		cls_bpf.command = TC_CLSBPF_OFFLOAD;
66662306a36Sopenharmony_ci		cls_bpf.exts = &prog->exts;
66762306a36Sopenharmony_ci		cls_bpf.prog = add ? prog->filter : NULL;
66862306a36Sopenharmony_ci		cls_bpf.oldprog = add ? NULL : prog->filter;
66962306a36Sopenharmony_ci		cls_bpf.name = prog->bpf_name;
67062306a36Sopenharmony_ci		cls_bpf.exts_integrated = prog->exts_integrated;
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci		err = tc_setup_cb_reoffload(block, tp, add, cb, TC_SETUP_CLSBPF,
67362306a36Sopenharmony_ci					    &cls_bpf, cb_priv, &prog->gen_flags,
67462306a36Sopenharmony_ci					    &prog->in_hw_count);
67562306a36Sopenharmony_ci		if (err)
67662306a36Sopenharmony_ci			return err;
67762306a36Sopenharmony_ci	}
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_ci	return 0;
68062306a36Sopenharmony_ci}
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_cistatic struct tcf_proto_ops cls_bpf_ops __read_mostly = {
68362306a36Sopenharmony_ci	.kind		=	"bpf",
68462306a36Sopenharmony_ci	.owner		=	THIS_MODULE,
68562306a36Sopenharmony_ci	.classify	=	cls_bpf_classify,
68662306a36Sopenharmony_ci	.init		=	cls_bpf_init,
68762306a36Sopenharmony_ci	.destroy	=	cls_bpf_destroy,
68862306a36Sopenharmony_ci	.get		=	cls_bpf_get,
68962306a36Sopenharmony_ci	.change		=	cls_bpf_change,
69062306a36Sopenharmony_ci	.delete		=	cls_bpf_delete,
69162306a36Sopenharmony_ci	.walk		=	cls_bpf_walk,
69262306a36Sopenharmony_ci	.reoffload	=	cls_bpf_reoffload,
69362306a36Sopenharmony_ci	.dump		=	cls_bpf_dump,
69462306a36Sopenharmony_ci	.bind_class	=	cls_bpf_bind_class,
69562306a36Sopenharmony_ci};
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_cistatic int __init cls_bpf_init_mod(void)
69862306a36Sopenharmony_ci{
69962306a36Sopenharmony_ci	return register_tcf_proto_ops(&cls_bpf_ops);
70062306a36Sopenharmony_ci}
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_cistatic void __exit cls_bpf_exit_mod(void)
70362306a36Sopenharmony_ci{
70462306a36Sopenharmony_ci	unregister_tcf_proto_ops(&cls_bpf_ops);
70562306a36Sopenharmony_ci}
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_cimodule_init(cls_bpf_init_mod);
70862306a36Sopenharmony_cimodule_exit(cls_bpf_exit_mod);
709