cls_bpf: introduce integrated actions
Often cls_bpf classifier is used with single action drop attached. Optimize this use case and let cls_bpf return both classid and action. For backwards compatibility reasons enable this feature under TCA_BPF_FLAG_ACT_DIRECT flag. Then more interesting programs like the following are easier to write: int cls_bpf_prog(struct __sk_buff *skb) { /* classify arp, ip, ipv6 into different traffic classes * and drop all other packets */ switch (skb->protocol) { case htons(ETH_P_ARP): skb->tc_classid = 1; break; case htons(ETH_P_IP): skb->tc_classid = 2; break; case htons(ETH_P_IPV6): skb->tc_classid = 3; break; default: return TC_ACT_SHOT; } return TC_ACT_OK; } Joint work with Daniel Borkmann. Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Signed-off-by: Alexei Starovoitov <ast@plumgrid.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Родитель
f6c53334d6
Коммит
045efa82ff
|
@ -251,7 +251,7 @@ struct tcf_proto {
|
||||||
struct qdisc_skb_cb {
|
struct qdisc_skb_cb {
|
||||||
unsigned int pkt_len;
|
unsigned int pkt_len;
|
||||||
u16 slave_dev_queue_mapping;
|
u16 slave_dev_queue_mapping;
|
||||||
u16 _pad;
|
u16 tc_classid;
|
||||||
#define QDISC_CB_PRIV_LEN 20
|
#define QDISC_CB_PRIV_LEN 20
|
||||||
unsigned char data[QDISC_CB_PRIV_LEN];
|
unsigned char data[QDISC_CB_PRIV_LEN];
|
||||||
};
|
};
|
||||||
|
|
|
@ -293,6 +293,7 @@ struct __sk_buff {
|
||||||
__u32 tc_index;
|
__u32 tc_index;
|
||||||
__u32 cb[5];
|
__u32 cb[5];
|
||||||
__u32 hash;
|
__u32 hash;
|
||||||
|
__u32 tc_classid;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct bpf_tunnel_key {
|
struct bpf_tunnel_key {
|
||||||
|
|
|
@ -373,6 +373,8 @@ enum {
|
||||||
|
|
||||||
/* BPF classifier */
|
/* BPF classifier */
|
||||||
|
|
||||||
|
#define TCA_BPF_FLAG_ACT_DIRECT (1 << 0)
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
TCA_BPF_UNSPEC,
|
TCA_BPF_UNSPEC,
|
||||||
TCA_BPF_ACT,
|
TCA_BPF_ACT,
|
||||||
|
@ -382,6 +384,7 @@ enum {
|
||||||
TCA_BPF_OPS,
|
TCA_BPF_OPS,
|
||||||
TCA_BPF_FD,
|
TCA_BPF_FD,
|
||||||
TCA_BPF_NAME,
|
TCA_BPF_NAME,
|
||||||
|
TCA_BPF_FLAGS,
|
||||||
__TCA_BPF_MAX,
|
__TCA_BPF_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1632,6 +1632,9 @@ static bool __is_valid_access(int off, int size, enum bpf_access_type type)
|
||||||
static bool sk_filter_is_valid_access(int off, int size,
|
static bool sk_filter_is_valid_access(int off, int size,
|
||||||
enum bpf_access_type type)
|
enum bpf_access_type type)
|
||||||
{
|
{
|
||||||
|
if (off == offsetof(struct __sk_buff, tc_classid))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (type == BPF_WRITE) {
|
if (type == BPF_WRITE) {
|
||||||
switch (off) {
|
switch (off) {
|
||||||
case offsetof(struct __sk_buff, cb[0]) ...
|
case offsetof(struct __sk_buff, cb[0]) ...
|
||||||
|
@ -1648,6 +1651,9 @@ static bool sk_filter_is_valid_access(int off, int size,
|
||||||
static bool tc_cls_act_is_valid_access(int off, int size,
|
static bool tc_cls_act_is_valid_access(int off, int size,
|
||||||
enum bpf_access_type type)
|
enum bpf_access_type type)
|
||||||
{
|
{
|
||||||
|
if (off == offsetof(struct __sk_buff, tc_classid))
|
||||||
|
return type == BPF_WRITE ? true : false;
|
||||||
|
|
||||||
if (type == BPF_WRITE) {
|
if (type == BPF_WRITE) {
|
||||||
switch (off) {
|
switch (off) {
|
||||||
case offsetof(struct __sk_buff, mark):
|
case offsetof(struct __sk_buff, mark):
|
||||||
|
@ -1760,6 +1766,14 @@ static u32 bpf_net_convert_ctx_access(enum bpf_access_type type, int dst_reg,
|
||||||
*insn++ = BPF_LDX_MEM(BPF_W, dst_reg, src_reg, ctx_off);
|
*insn++ = BPF_LDX_MEM(BPF_W, dst_reg, src_reg, ctx_off);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case offsetof(struct __sk_buff, tc_classid):
|
||||||
|
ctx_off -= offsetof(struct __sk_buff, tc_classid);
|
||||||
|
ctx_off += offsetof(struct sk_buff, cb);
|
||||||
|
ctx_off += offsetof(struct qdisc_skb_cb, tc_classid);
|
||||||
|
WARN_ON(type != BPF_WRITE);
|
||||||
|
*insn++ = BPF_STX_MEM(BPF_H, dst_reg, src_reg, ctx_off);
|
||||||
|
break;
|
||||||
|
|
||||||
case offsetof(struct __sk_buff, tc_index):
|
case offsetof(struct __sk_buff, tc_index):
|
||||||
#ifdef CONFIG_NET_SCHED
|
#ifdef CONFIG_NET_SCHED
|
||||||
BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, tc_index) != 2);
|
BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, tc_index) != 2);
|
||||||
|
|
|
@ -38,6 +38,7 @@ struct cls_bpf_prog {
|
||||||
struct bpf_prog *filter;
|
struct bpf_prog *filter;
|
||||||
struct list_head link;
|
struct list_head link;
|
||||||
struct tcf_result res;
|
struct tcf_result res;
|
||||||
|
bool exts_integrated;
|
||||||
struct tcf_exts exts;
|
struct tcf_exts exts;
|
||||||
u32 handle;
|
u32 handle;
|
||||||
union {
|
union {
|
||||||
|
@ -52,6 +53,7 @@ struct cls_bpf_prog {
|
||||||
|
|
||||||
static const struct nla_policy bpf_policy[TCA_BPF_MAX + 1] = {
|
static const struct nla_policy bpf_policy[TCA_BPF_MAX + 1] = {
|
||||||
[TCA_BPF_CLASSID] = { .type = NLA_U32 },
|
[TCA_BPF_CLASSID] = { .type = NLA_U32 },
|
||||||
|
[TCA_BPF_FLAGS] = { .type = NLA_U32 },
|
||||||
[TCA_BPF_FD] = { .type = NLA_U32 },
|
[TCA_BPF_FD] = { .type = NLA_U32 },
|
||||||
[TCA_BPF_NAME] = { .type = NLA_NUL_STRING, .len = CLS_BPF_NAME_LEN },
|
[TCA_BPF_NAME] = { .type = NLA_NUL_STRING, .len = CLS_BPF_NAME_LEN },
|
||||||
[TCA_BPF_OPS_LEN] = { .type = NLA_U16 },
|
[TCA_BPF_OPS_LEN] = { .type = NLA_U16 },
|
||||||
|
@ -59,6 +61,22 @@ static const struct nla_policy bpf_policy[TCA_BPF_MAX + 1] = {
|
||||||
.len = sizeof(struct sock_filter) * BPF_MAXINSNS },
|
.len = sizeof(struct sock_filter) * BPF_MAXINSNS },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int cls_bpf_exec_opcode(int code)
|
||||||
|
{
|
||||||
|
switch (code) {
|
||||||
|
case TC_ACT_OK:
|
||||||
|
case TC_ACT_RECLASSIFY:
|
||||||
|
case TC_ACT_SHOT:
|
||||||
|
case TC_ACT_PIPE:
|
||||||
|
case TC_ACT_STOLEN:
|
||||||
|
case TC_ACT_QUEUED:
|
||||||
|
case TC_ACT_UNSPEC:
|
||||||
|
return code;
|
||||||
|
default:
|
||||||
|
return TC_ACT_UNSPEC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int cls_bpf_classify(struct sk_buff *skb, const struct tcf_proto *tp,
|
static int cls_bpf_classify(struct sk_buff *skb, const struct tcf_proto *tp,
|
||||||
struct tcf_result *res)
|
struct tcf_result *res)
|
||||||
{
|
{
|
||||||
|
@ -79,6 +97,8 @@ static int cls_bpf_classify(struct sk_buff *skb, const struct tcf_proto *tp,
|
||||||
list_for_each_entry_rcu(prog, &head->plist, link) {
|
list_for_each_entry_rcu(prog, &head->plist, link) {
|
||||||
int filter_res;
|
int filter_res;
|
||||||
|
|
||||||
|
qdisc_skb_cb(skb)->tc_classid = prog->res.classid;
|
||||||
|
|
||||||
if (at_ingress) {
|
if (at_ingress) {
|
||||||
/* It is safe to push/pull even if skb_shared() */
|
/* It is safe to push/pull even if skb_shared() */
|
||||||
__skb_push(skb, skb->mac_len);
|
__skb_push(skb, skb->mac_len);
|
||||||
|
@ -88,6 +108,16 @@ static int cls_bpf_classify(struct sk_buff *skb, const struct tcf_proto *tp,
|
||||||
filter_res = BPF_PROG_RUN(prog->filter, skb);
|
filter_res = BPF_PROG_RUN(prog->filter, skb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prog->exts_integrated) {
|
||||||
|
res->class = prog->res.class;
|
||||||
|
res->classid = qdisc_skb_cb(skb)->tc_classid;
|
||||||
|
|
||||||
|
ret = cls_bpf_exec_opcode(filter_res);
|
||||||
|
if (ret == TC_ACT_UNSPEC)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (filter_res == 0)
|
if (filter_res == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -195,8 +225,7 @@ static unsigned long cls_bpf_get(struct tcf_proto *tp, u32 handle)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cls_bpf_prog_from_ops(struct nlattr **tb,
|
static int cls_bpf_prog_from_ops(struct nlattr **tb, struct cls_bpf_prog *prog)
|
||||||
struct cls_bpf_prog *prog, u32 classid)
|
|
||||||
{
|
{
|
||||||
struct sock_filter *bpf_ops;
|
struct sock_filter *bpf_ops;
|
||||||
struct sock_fprog_kern fprog_tmp;
|
struct sock_fprog_kern fprog_tmp;
|
||||||
|
@ -230,15 +259,13 @@ static int cls_bpf_prog_from_ops(struct nlattr **tb,
|
||||||
prog->bpf_ops = bpf_ops;
|
prog->bpf_ops = bpf_ops;
|
||||||
prog->bpf_num_ops = bpf_num_ops;
|
prog->bpf_num_ops = bpf_num_ops;
|
||||||
prog->bpf_name = NULL;
|
prog->bpf_name = NULL;
|
||||||
|
|
||||||
prog->filter = fp;
|
prog->filter = fp;
|
||||||
prog->res.classid = classid;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cls_bpf_prog_from_efd(struct nlattr **tb,
|
static int cls_bpf_prog_from_efd(struct nlattr **tb, struct cls_bpf_prog *prog,
|
||||||
struct cls_bpf_prog *prog, u32 classid)
|
const struct tcf_proto *tp)
|
||||||
{
|
{
|
||||||
struct bpf_prog *fp;
|
struct bpf_prog *fp;
|
||||||
char *name = NULL;
|
char *name = NULL;
|
||||||
|
@ -268,9 +295,7 @@ static int cls_bpf_prog_from_efd(struct nlattr **tb,
|
||||||
prog->bpf_ops = NULL;
|
prog->bpf_ops = NULL;
|
||||||
prog->bpf_fd = bpf_fd;
|
prog->bpf_fd = bpf_fd;
|
||||||
prog->bpf_name = name;
|
prog->bpf_name = name;
|
||||||
|
|
||||||
prog->filter = fp;
|
prog->filter = fp;
|
||||||
prog->res.classid = classid;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -280,8 +305,8 @@ static int cls_bpf_modify_existing(struct net *net, struct tcf_proto *tp,
|
||||||
unsigned long base, struct nlattr **tb,
|
unsigned long base, struct nlattr **tb,
|
||||||
struct nlattr *est, bool ovr)
|
struct nlattr *est, bool ovr)
|
||||||
{
|
{
|
||||||
|
bool is_bpf, is_ebpf, have_exts = false;
|
||||||
struct tcf_exts exts;
|
struct tcf_exts exts;
|
||||||
bool is_bpf, is_ebpf;
|
|
||||||
u32 classid;
|
u32 classid;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
@ -298,9 +323,22 @@ static int cls_bpf_modify_existing(struct net *net, struct tcf_proto *tp,
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
classid = nla_get_u32(tb[TCA_BPF_CLASSID]);
|
classid = nla_get_u32(tb[TCA_BPF_CLASSID]);
|
||||||
|
if (tb[TCA_BPF_FLAGS]) {
|
||||||
|
u32 bpf_flags = nla_get_u32(tb[TCA_BPF_FLAGS]);
|
||||||
|
|
||||||
ret = is_bpf ? cls_bpf_prog_from_ops(tb, prog, classid) :
|
if (bpf_flags & ~TCA_BPF_FLAG_ACT_DIRECT) {
|
||||||
cls_bpf_prog_from_efd(tb, prog, classid);
|
tcf_exts_destroy(&exts);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
have_exts = bpf_flags & TCA_BPF_FLAG_ACT_DIRECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
prog->res.classid = classid;
|
||||||
|
prog->exts_integrated = have_exts;
|
||||||
|
|
||||||
|
ret = is_bpf ? cls_bpf_prog_from_ops(tb, prog) :
|
||||||
|
cls_bpf_prog_from_efd(tb, prog, tp);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
tcf_exts_destroy(&exts);
|
tcf_exts_destroy(&exts);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче