|
9 | 9 | * Eduardo J. Blanco <ejbs@netlabs.com.uy> :990222: kmod support |
10 | 10 | */ |
11 | 11 |
|
| 12 | +#include <linux/bpf.h> |
12 | 13 | #include <linux/module.h> |
13 | 14 | #include <linux/types.h> |
14 | 15 | #include <linux/kernel.h> |
@@ -1720,9 +1721,9 @@ static struct tcf_proto *tcf_chain_tp_insert_unique(struct tcf_chain *chain, |
1720 | 1721 | return tp_new; |
1721 | 1722 | } |
1722 | 1723 |
|
1723 | | -static void tcf_chain_tp_delete_empty(struct tcf_chain *chain, |
1724 | | - struct tcf_proto *tp, bool rtnl_held, |
1725 | | - struct netlink_ext_ack *extack) |
| 1724 | +void tcf_chain_tp_delete_empty(struct tcf_chain *chain, |
| 1725 | + struct tcf_proto *tp, bool rtnl_held, |
| 1726 | + struct netlink_ext_ack *extack) |
1726 | 1727 | { |
1727 | 1728 | struct tcf_chain_info chain_info; |
1728 | 1729 | struct tcf_proto *tp_iter; |
@@ -1760,6 +1761,7 @@ static void tcf_chain_tp_delete_empty(struct tcf_chain *chain, |
1760 | 1761 |
|
1761 | 1762 | tcf_proto_put(tp, rtnl_held, extack); |
1762 | 1763 | } |
| 1764 | +EXPORT_SYMBOL_GPL(tcf_chain_tp_delete_empty); |
1763 | 1765 |
|
1764 | 1766 | static struct tcf_proto *tcf_chain_tp_find(struct tcf_chain *chain, |
1765 | 1767 | struct tcf_chain_info *chain_info, |
@@ -3917,3 +3919,134 @@ static int __init tc_filter_init(void) |
3917 | 3919 | } |
3918 | 3920 |
|
3919 | 3921 | subsys_initcall(tc_filter_init); |
| 3922 | + |
| 3923 | +#if IS_ENABLED(CONFIG_NET_CLS_BPF) |
| 3924 | + |
| 3925 | +int bpf_tc_link_attach(union bpf_attr *attr, struct bpf_prog *prog) |
| 3926 | +{ |
| 3927 | + struct net *net = current->nsproxy->net_ns; |
| 3928 | + struct tcf_chain_info chain_info; |
| 3929 | + u32 chain_index, prio, parent; |
| 3930 | + struct tcf_block *block; |
| 3931 | + struct tcf_chain *chain; |
| 3932 | + struct tcf_proto *tp; |
| 3933 | + int err, tp_created; |
| 3934 | + unsigned long cl; |
| 3935 | + struct Qdisc *q; |
| 3936 | + __be16 protocol; |
| 3937 | + void *fh; |
| 3938 | + |
| 3939 | + /* Caller already checks bpf_capable */ |
| 3940 | + if (!ns_capable(current->nsproxy->net_ns->user_ns, CAP_NET_ADMIN)) |
| 3941 | + return -EPERM; |
| 3942 | + |
| 3943 | + if (attr->link_create.flags || |
| 3944 | + !attr->link_create.target_ifindex || |
| 3945 | + !tc_flags_valid(attr->link_create.tc.gen_flags)) |
| 3946 | + return -EINVAL; |
| 3947 | + |
| 3948 | +replay: |
| 3949 | + parent = attr->link_create.tc.parent; |
| 3950 | + prio = attr->link_create.tc.priority; |
| 3951 | + protocol = htons(ETH_P_ALL); |
| 3952 | + chain_index = 0; |
| 3953 | + tp_created = 0; |
| 3954 | + prio <<= 16; |
| 3955 | + cl = 0; |
| 3956 | + |
| 3957 | + /* Address this when cls_bpf switches to RTNL_FLAG_DOIT_UNLOCKED */ |
| 3958 | + rtnl_lock(); |
| 3959 | + |
| 3960 | + block = tcf_block_find(net, &q, &parent, &cl, |
| 3961 | + attr->link_create.target_ifindex, parent, NULL); |
| 3962 | + if (IS_ERR(block)) { |
| 3963 | + err = PTR_ERR(block); |
| 3964 | + goto out_unlock; |
| 3965 | + } |
| 3966 | + block->classid = parent; |
| 3967 | + |
| 3968 | + chain = tcf_chain_get(block, chain_index, true); |
| 3969 | + if (!chain) { |
| 3970 | + err = -ENOMEM; |
| 3971 | + goto out_block; |
| 3972 | + } |
| 3973 | + |
| 3974 | + mutex_lock(&chain->filter_chain_lock); |
| 3975 | + |
| 3976 | + tp = tcf_chain_tp_find(chain, &chain_info, protocol, |
| 3977 | + prio ?: TC_H_MAKE(0x80000000U, 0U), |
| 3978 | + !prio); |
| 3979 | + if (IS_ERR(tp)) { |
| 3980 | + err = PTR_ERR(tp); |
| 3981 | + goto out_chain_unlock; |
| 3982 | + } |
| 3983 | + |
| 3984 | + if (!tp) { |
| 3985 | + struct tcf_proto *tp_new = NULL; |
| 3986 | + |
| 3987 | + if (chain->flushing) { |
| 3988 | + err = -EAGAIN; |
| 3989 | + goto out_chain_unlock; |
| 3990 | + } |
| 3991 | + |
| 3992 | + if (!prio) |
| 3993 | + prio = tcf_auto_prio(tcf_chain_tp_prev(chain, |
| 3994 | + &chain_info)); |
| 3995 | + |
| 3996 | + mutex_unlock(&chain->filter_chain_lock); |
| 3997 | + |
| 3998 | + tp_new = tcf_proto_create("bpf", protocol, prio, chain, true, |
| 3999 | + NULL); |
| 4000 | + if (IS_ERR(tp_new)) { |
| 4001 | + err = PTR_ERR(tp_new); |
| 4002 | + goto out_chain; |
| 4003 | + } |
| 4004 | + |
| 4005 | + tp_created = 1; |
| 4006 | + tp = tcf_chain_tp_insert_unique(chain, tp_new, protocol, prio, |
| 4007 | + true); |
| 4008 | + if (IS_ERR(tp)) { |
| 4009 | + err = PTR_ERR(tp); |
| 4010 | + goto out_chain; |
| 4011 | + } |
| 4012 | + } else { |
| 4013 | + mutex_unlock(&chain->filter_chain_lock); |
| 4014 | + } |
| 4015 | + |
| 4016 | + fh = tp->ops->get(tp, attr->link_create.tc.handle); |
| 4017 | + |
| 4018 | + if (!tp->ops->bpf_link_change) |
| 4019 | + err = -EDEADLK; |
| 4020 | + else |
| 4021 | + err = tp->ops->bpf_link_change(net, tp, prog, &fh, |
| 4022 | + attr->link_create.tc.handle, |
| 4023 | + attr->link_create.tc.gen_flags); |
| 4024 | + if (err >= 0 && q) |
| 4025 | + q->flags &= ~TCQ_F_CAN_BYPASS; |
| 4026 | + |
| 4027 | +out: |
| 4028 | + if (err < 0 && tp_created) |
| 4029 | + tcf_chain_tp_delete_empty(chain, tp, true, NULL); |
| 4030 | +out_chain: |
| 4031 | + if (chain) { |
| 4032 | + if (!IS_ERR_OR_NULL(tp)) |
| 4033 | + tcf_proto_put(tp, true, NULL); |
| 4034 | + /* Chain reference only kept for tp creation |
| 4035 | + * to pair with tcf_chain_put from tcf_proto_destroy |
| 4036 | + */ |
| 4037 | + if (!tp_created) |
| 4038 | + tcf_chain_put(chain); |
| 4039 | + } |
| 4040 | +out_block: |
| 4041 | + tcf_block_release(q, block, true); |
| 4042 | +out_unlock: |
| 4043 | + rtnl_unlock(); |
| 4044 | + if (err == -EAGAIN) |
| 4045 | + goto replay; |
| 4046 | + return err; |
| 4047 | +out_chain_unlock: |
| 4048 | + mutex_unlock(&chain->filter_chain_lock); |
| 4049 | + goto out; |
| 4050 | +} |
| 4051 | + |
| 4052 | +#endif |
0 commit comments