@@ -325,6 +325,20 @@ struct tc_skb_ext {
};
#endif
+#if IS_ENABLED(CONFIG_NET_P4_TC)
+#include <uapi/linux/p4tc.h>
+
+struct __p4tc_skb_ext {
+ u8 key[BITS_TO_BYTES(P4TC_MAX_KEYSZ)];
+ u8 hdrs[HEADER_MAX_LEN];
+ u8 metadata[META_MAX_LEN];
+};
+
+struct p4tc_skb_ext {
+ struct __p4tc_skb_ext *p4tc_ext;
+};
+#endif
+
struct sk_buff_head {
/* These two members must be first to match sk_buff. */
struct_group_tagged(sk_buff_list, list,
@@ -4571,6 +4585,9 @@ enum skb_ext_id {
#if IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
TC_SKB_EXT,
#endif
+#if IS_ENABLED(CONFIG_NET_P4_TC)
+ P4TC_SKB_EXT,
+#endif
#if IS_ENABLED(CONFIG_MPTCP)
SKB_EXT_MPTCP,
#endif
@@ -12,11 +12,13 @@
#define P4TC_DEFAULT_NUM_TABLES P4TC_MINTABLES_COUNT
#define P4TC_DEFAULT_MAX_RULES 1
+#define P4TC_MAXMETA_OFFSET 512
#define P4TC_PATH_MAX 3
#define P4TC_KERNEL_PIPEID 0
#define P4TC_PID_IDX 0
+#define P4TC_MID_IDX 1
struct p4tc_dump_ctx {
u32 ids[P4TC_PATH_MAX];
@@ -78,6 +80,7 @@ extern const struct p4tc_template_ops p4tc_pipeline_ops;
struct p4tc_pipeline {
struct p4tc_template_common common;
+ struct idr p_meta_idr;
struct rcu_head rcu;
struct net *net;
struct tc_action **preacts;
@@ -85,6 +88,7 @@ struct p4tc_pipeline {
struct tc_action **postacts;
int num_postacts;
u32 max_rules;
+ u32 p_meta_offset;
refcount_t p_ref;
refcount_t p_ctrl_ref;
u16 num_tables;
@@ -126,6 +130,36 @@ static inline int p4tc_action_destroy(struct tc_action **acts)
return ret;
}
+static inline bool pipeline_sealed(struct p4tc_pipeline *pipeline)
+{
+ return pipeline->p_state == P4TC_STATE_READY;
+}
+
+struct p4tc_metadata {
+ struct p4tc_template_common common;
+ struct rcu_head rcu;
+ u32 m_id;
+ u32 m_skb_off;
+ refcount_t m_ref;
+ u16 m_sz;
+ u16 m_startbit; /* Relative to its container */
+ u16 m_endbit; /* Relative to its container */
+ u8 m_datatype; /* T_XXX */
+ bool m_read_only;
+};
+
+extern const struct p4tc_template_ops p4tc_meta_ops;
+
+struct p4tc_metadata *tcf_meta_find_byid(struct p4tc_pipeline *pipeline,
+ u32 m_id);
+void tcf_meta_fill_user_offsets(struct p4tc_pipeline *pipeline);
+void tcf_meta_init(struct p4tc_pipeline *root_pipe);
+struct p4tc_metadata *tcf_meta_get(struct p4tc_pipeline *pipeline,
+ const char *mname, const u32 m_id,
+ struct netlink_ext_ack *extack);
+void tcf_meta_put_ref(struct p4tc_metadata *meta);
+
#define to_pipeline(t) ((struct p4tc_pipeline *)t)
+#define to_meta(t) ((struct p4tc_metadata *)t)
#endif
@@ -18,11 +18,15 @@ struct p4tcmsg {
#define P4TC_MAXPARSE_KEYS 16
#define P4TC_MAXMETA_SZ 128
#define P4TC_MSGBATCH_SIZE 16
+#define P4TC_MAX_KEYSZ 512
+#define HEADER_MAX_LEN 512
+#define META_MAX_LEN 512
#define P4TC_MAX_KEYSZ 512
#define TEMPLATENAMSZ 256
#define PIPELINENAMSIZ TEMPLATENAMSZ
+#define METANAMSIZ TEMPLATENAMSZ
/* Root attributes */
enum {
@@ -50,6 +54,7 @@ enum {
enum {
P4TC_OBJ_UNSPEC,
P4TC_OBJ_PIPELINE,
+ P4TC_OBJ_META,
__P4TC_OBJ_MAX,
};
#define P4TC_OBJ_MAX __P4TC_OBJ_MAX
@@ -59,6 +64,7 @@ enum {
P4TC_UNSPEC,
P4TC_PATH,
P4TC_PARAMS,
+ P4TC_COUNT,
__P4TC_MAX,
};
#define P4TC_MAX __P4TC_MAX
@@ -102,6 +108,51 @@ enum {
};
#define P4T_MAX (__P4T_MAX - 1)
+/* Details all the info needed to find out metadata size and layout inside cb
+ * datastructure
+ */
+struct p4tc_meta_size_params {
+ __u16 startbit;
+ __u16 endbit;
+ __u8 datatype; /* T_XXX */
+};
+
+/* Metadata attributes */
+enum {
+ P4TC_META_UNSPEC,
+ P4TC_META_NAME, /* string */
+ P4TC_META_SIZE, /* struct p4tc_meta_size_params */
+ __P4TC_META_MAX
+};
+#define P4TC_META_MAX __P4TC_META_MAX
+
+/* Linux system metadata */
+enum {
+ P4TC_KERNEL_META_UNSPEC,
+ P4TC_KERNEL_META_PKTLEN, /* u32 */
+ P4TC_KERNEL_META_DATALEN, /* u32 */
+ P4TC_KERNEL_META_SKBMARK, /* u32 */
+ P4TC_KERNEL_META_TCINDEX, /* u16 */
+ P4TC_KERNEL_META_SKBHASH, /* u32 */
+ P4TC_KERNEL_META_SKBPRIO, /* u32 */
+ P4TC_KERNEL_META_IFINDEX, /* s32 */
+ P4TC_KERNEL_META_SKBIIF, /* s32 */
+ P4TC_KERNEL_META_PROTOCOL, /* be16 */
+ P4TC_KERNEL_META_PKTYPE, /* u8:3 */
+ P4TC_KERNEL_META_IDF, /* u8:1 */
+ P4TC_KERNEL_META_IPSUM, /* u8:2 */
+ P4TC_KERNEL_META_OOOK, /* u8:1 */
+ P4TC_KERNEL_META_FCLONE, /* u8:2 */
+ P4TC_KERNEL_META_PEEKED, /* u8:1 */
+ P4TC_KERNEL_META_QMAP, /* u16 */
+ P4TC_KERNEL_META_PTYPEOFF, /* u8 */
+ P4TC_KERNEL_META_CLONEOFF, /* u8 */
+ P4TC_KERNEL_META_PTCLNOFF, /* u16 */
+ P4TC_KERNEL_META_DIRECTION, /* u8:1 */
+ __P4TC_KERNEL_META_MAX
+};
+#define P4TC_KERNEL_META_MAX (__P4TC_KERNEL_META_MAX - 1)
+
#define P4TC_RTA(r) \
((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct p4tcmsg))))
@@ -4583,6 +4583,9 @@ static const u8 skb_ext_type_len[] = {
#if IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
[TC_SKB_EXT] = SKB_EXT_CHUNKSIZEOF(struct tc_skb_ext),
#endif
+#if IS_ENABLED(CONFIG_NET_P4_TC)
+ [P4TC_SKB_EXT] = SKB_EXT_CHUNKSIZEOF(struct p4tc_skb_ext),
+#endif
#if IS_ENABLED(CONFIG_MPTCP)
[SKB_EXT_MPTCP] = SKB_EXT_CHUNKSIZEOF(struct mptcp_ext),
#endif
@@ -4603,6 +4606,9 @@ static __always_inline unsigned int skb_ext_total_length(void)
#if IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
skb_ext_type_len[TC_SKB_EXT] +
#endif
+#if IS_ENABLED(CONFIG_NET_P4_TC)
+ skb_ext_type_len[P4TC_SKB_EXT] +
+#endif
#if IS_ENABLED(CONFIG_MPTCP)
skb_ext_type_len[SKB_EXT_MPTCP] +
#endif
@@ -6685,6 +6691,13 @@ static void skb_ext_put_mctp(struct mctp_flow *flow)
}
#endif
+#ifdef CONFIG_NET_P4_TC
+static void skb_ext_put_p4tc(struct p4tc_skb_ext *p4tc_skb_ext)
+{
+ kfree(p4tc_skb_ext->p4tc_ext);
+}
+#endif
+
void __skb_ext_del(struct sk_buff *skb, enum skb_ext_id id)
{
struct skb_ext *ext = skb->extensions;
@@ -6724,6 +6737,10 @@ void __skb_ext_put(struct skb_ext *ext)
if (__skb_ext_exist(ext, SKB_EXT_MCTP))
skb_ext_put_mctp(skb_ext_get_ptr(ext, SKB_EXT_MCTP));
#endif
+#ifdef CONFIG_NET_P4_TC
+ if (__skb_ext_exist(ext, P4TC_SKB_EXT))
+ skb_ext_put_p4tc(skb_ext_get_ptr(ext, P4TC_SKB_EXT));
+#endif
kmem_cache_free(skbuff_ext_cache, ext);
}
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
-obj-y := p4tc_types.o p4tc_tmpl_api.o p4tc_pipeline.o
+obj-y := p4tc_types.o p4tc_tmpl_api.o p4tc_pipeline.o p4tc_meta.o
new file mode 100644
@@ -0,0 +1,819 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * net/sched/p4tc_meta.c P4 TC API METADATA
+ *
+ * Copyright (c) 2022, Mojatatu Networks
+ * Copyright (c) 2022, Intel Corporation.
+ * Authors: Jamal Hadi Salim <jhs@mojatatu.com>
+ * Victor Nogueira <victor@mojatatu.com>
+ * Pedro Tammela <pctammela@mojatatu.com>
+ */
+
+#include <asm/byteorder.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <linux/kmod.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <net/net_namespace.h>
+#include <net/sock.h>
+#include <net/sch_generic.h>
+#include <net/pkt_cls.h>
+#include <net/p4tc.h>
+#include <net/netlink.h>
+#include <net/flow_offload.h>
+#include <net/p4tc_types.h>
+
+#define START_META_OFFSET 0
+
+static const struct nla_policy p4tc_meta_policy[P4TC_META_MAX + 1] = {
+ [P4TC_META_NAME] = { .type = NLA_STRING, .len = METANAMSIZ },
+ [P4TC_META_SIZE] = { .type = NLA_BINARY,
+ .len = sizeof(struct p4tc_meta_size_params) },
+};
+
+static int _tcf_meta_put(struct p4tc_pipeline *pipeline,
+ struct p4tc_metadata *meta, bool unconditional_purge,
+ struct netlink_ext_ack *extack)
+{
+ if (!unconditional_purge && !refcount_dec_if_one(&meta->m_ref))
+ return -EBUSY;
+
+ pipeline->p_meta_offset -= BITS_TO_U32(meta->m_sz) * sizeof(u32);
+ idr_remove(&pipeline->p_meta_idr, meta->m_id);
+
+ kfree_rcu(meta, rcu);
+
+ return 0;
+}
+
+static int tcf_meta_put(struct net *net, struct p4tc_template_common *template,
+ bool unconditional_purge,
+ struct netlink_ext_ack *extack)
+{
+ struct p4tc_pipeline *pipeline =
+ tcf_pipeline_find_byid(net, template->p_id);
+ struct p4tc_metadata *meta = to_meta(template);
+ int ret;
+
+ ret = _tcf_meta_put(pipeline, meta, unconditional_purge, extack);
+ if (ret < 0)
+ NL_SET_ERR_MSG(extack, "Unable to delete referenced metadatum");
+
+ return ret;
+}
+
+struct p4tc_metadata *tcf_meta_find_byid(struct p4tc_pipeline *pipeline,
+ u32 m_id)
+{
+ return idr_find(&pipeline->p_meta_idr, m_id);
+}
+
+static struct p4tc_metadata *
+tcf_meta_find_byname(const char *m_name, struct p4tc_pipeline *pipeline)
+{
+ struct p4tc_metadata *meta;
+ unsigned long tmp, id;
+
+ idr_for_each_entry_ul(&pipeline->p_meta_idr, meta, tmp, id)
+ if (strncmp(meta->common.name, m_name, METANAMSIZ) == 0)
+ return meta;
+
+ return NULL;
+}
+
+static inline struct p4tc_metadata *
+tcf_meta_find_byname_attr(struct nlattr *name_attr,
+ struct p4tc_pipeline *pipeline)
+{
+ return tcf_meta_find_byname(nla_data(name_attr), pipeline);
+}
+
+static struct p4tc_metadata *tcf_meta_find_byany(struct p4tc_pipeline *pipeline,
+ const char *mname,
+ const u32 m_id,
+ struct netlink_ext_ack *extack)
+{
+ struct p4tc_metadata *meta;
+ int err;
+
+ if (m_id) {
+ meta = tcf_meta_find_byid(pipeline, m_id);
+ if (!meta) {
+ NL_SET_ERR_MSG(extack,
+ "Unable to find metadatum by id");
+ err = -EINVAL;
+ goto out;
+ }
+ } else {
+ if (mname) {
+ meta = tcf_meta_find_byname(mname, pipeline);
+ if (!meta) {
+ NL_SET_ERR_MSG(extack,
+ "Metadatum name not found");
+ err = -EINVAL;
+ goto out;
+ }
+ } else {
+ NL_SET_ERR_MSG(extack,
+ "Must specify metadatum name or id");
+ err = -EINVAL;
+ goto out;
+ }
+ }
+
+ return meta;
+out:
+ return ERR_PTR(err);
+}
+
+struct p4tc_metadata *tcf_meta_get(struct p4tc_pipeline *pipeline,
+ const char *mname, const u32 m_id,
+ struct netlink_ext_ack *extack)
+{
+ struct p4tc_metadata *meta;
+
+ meta = tcf_meta_find_byany(pipeline, mname, m_id, extack);
+ if (IS_ERR(meta))
+ return meta;
+
+ /* Should never be zero */
+ WARN_ON(!refcount_inc_not_zero(&meta->m_ref));
+ return meta;
+}
+
+void tcf_meta_put_ref(struct p4tc_metadata *meta)
+{
+ WARN_ON(!refcount_dec_not_one(&meta->m_ref));
+}
+
+static struct p4tc_metadata *
+tcf_meta_find_byanyattr(struct p4tc_pipeline *pipeline,
+ struct nlattr *name_attr, const u32 m_id,
+ struct netlink_ext_ack *extack)
+{
+ char *mname = NULL;
+
+ if (name_attr)
+ mname = nla_data(name_attr);
+
+ return tcf_meta_find_byany(pipeline, mname, m_id, extack);
+}
+
+static int p4tc_check_meta_size(struct p4tc_meta_size_params *sz_params,
+ struct p4tc_type *type,
+ struct netlink_ext_ack *extack)
+{
+ int new_bitsz;
+
+ if (sz_params->startbit > P4T_MAX_BITSZ ||
+ sz_params->startbit > type->bitsz) {
+ NL_SET_ERR_MSG(extack, "Startbit value too big");
+ return -EINVAL;
+ }
+
+ if (sz_params->endbit > P4T_MAX_BITSZ ||
+ sz_params->endbit > type->bitsz) {
+ NL_SET_ERR_MSG(extack, "Endbit value too big");
+ return -EINVAL;
+ }
+
+ if (sz_params->endbit < sz_params->startbit) {
+ NL_SET_ERR_MSG(extack, "Endbit value smaller than startbit");
+ return -EINVAL;
+ }
+
+ new_bitsz = (sz_params->endbit - sz_params->startbit + 1);
+ if (new_bitsz == 0) {
+ NL_SET_ERR_MSG(extack, "Bit size can't be zero");
+ return -EINVAL;
+ }
+
+ if (new_bitsz > P4T_MAX_BITSZ || new_bitsz > type->bitsz) {
+ NL_SET_ERR_MSG(extack, "Bit size too big");
+ return -EINVAL;
+ }
+
+ return new_bitsz;
+}
+
+void tcf_meta_fill_user_offsets(struct p4tc_pipeline *pipeline)
+{
+ u32 meta_off = START_META_OFFSET;
+ struct p4tc_metadata *meta;
+ unsigned long tmp, id;
+
+ idr_for_each_entry_ul(&pipeline->p_meta_idr, meta, tmp, id) {
+ /* Offsets are multiples of 4 for alignment purposes */
+ meta->m_skb_off = meta_off;
+ meta_off += BITS_TO_U32(meta->m_sz) * sizeof(u32);
+ }
+}
+
+static struct p4tc_metadata *
+__tcf_meta_create(struct p4tc_pipeline *pipeline, u32 m_id, const char *m_name,
+ struct p4tc_meta_size_params *sz_params, gfp_t alloc_flag,
+ bool read_only, struct netlink_ext_ack *extack)
+{
+ u32 p_meta_offset = 0;
+ bool kmeta;
+ struct p4tc_metadata *meta;
+ struct p4tc_type *datatype;
+ u32 sz_bytes;
+ int sz_bits;
+ int ret;
+
+ kmeta = pipeline->common.p_id == P4TC_KERNEL_PIPEID;
+
+ meta = kzalloc(sizeof(*meta), alloc_flag);
+ if (!meta) {
+ if (kmeta)
+ pr_err("Unable to allocate kernel metadatum");
+ else
+ NL_SET_ERR_MSG(extack,
+ "Unable to allocate user metadatum");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ meta->common.p_id = pipeline->common.p_id;
+
+ datatype = p4type_find_byid(sz_params->datatype);
+ if (!datatype) {
+ if (kmeta)
+ pr_err("Invalid data type for kernel metadataum %u\n",
+ sz_params->datatype);
+ else
+ NL_SET_ERR_MSG(extack,
+ "Invalid data type for user metdatum");
+ ret = -EINVAL;
+ goto free;
+ }
+
+ sz_bits = p4tc_check_meta_size(sz_params, datatype, extack);
+ if (sz_bits < 0) {
+ ret = sz_bits;
+ goto free;
+ }
+
+ sz_bytes = BITS_TO_U32(datatype->bitsz) * sizeof(u32);
+ if (!kmeta) {
+ p_meta_offset = pipeline->p_meta_offset + sz_bytes;
+ if (p_meta_offset > BITS_TO_BYTES(P4TC_MAXMETA_OFFSET)) {
+ NL_SET_ERR_MSG(extack, "Metadata max offset exceeded");
+ ret = -EINVAL;
+ goto free;
+ }
+ }
+
+ meta->m_datatype = datatype->typeid;
+ meta->m_startbit = sz_params->startbit;
+ meta->m_endbit = sz_params->endbit;
+ meta->m_sz = sz_bits;
+ meta->m_read_only = read_only;
+
+ if (m_id) {
+ ret = idr_alloc_u32(&pipeline->p_meta_idr, meta, &m_id, m_id,
+ alloc_flag);
+ if (ret < 0) {
+ if (kmeta)
+ pr_err("Unable to alloc kernel metadatum id %u\n",
+ m_id);
+ else
+ NL_SET_ERR_MSG(extack,
+ "Unable to alloc user metadatum id");
+ goto free;
+ }
+
+ meta->m_id = m_id;
+ } else {
+ meta->m_id = 1;
+
+ ret = idr_alloc_u32(&pipeline->p_meta_idr, meta, &meta->m_id,
+ UINT_MAX, alloc_flag);
+ if (ret < 0) {
+ if (kmeta)
+ pr_err("Unable to alloc kernel metadatum id %u\n",
+ meta->m_id);
+ else
+ NL_SET_ERR_MSG(extack,
+ "Unable to alloc metadatum id");
+ goto free;
+ }
+ }
+
+ if (!kmeta)
+ pipeline->p_meta_offset = p_meta_offset;
+
+ strscpy(meta->common.name, m_name, METANAMSIZ);
+ meta->common.ops = (struct p4tc_template_ops *)&p4tc_meta_ops;
+
+ refcount_set(&meta->m_ref, 1);
+
+ return meta;
+
+free:
+ kfree(meta);
+out:
+ return ERR_PTR(ret);
+}
+
+struct p4tc_metadata *tcf_meta_create(struct nlmsghdr *n, struct nlattr *nla,
+ u32 m_id, struct p4tc_pipeline *pipeline,
+ struct netlink_ext_ack *extack)
+{
+ int ret = 0;
+ struct p4tc_meta_size_params *sz_params;
+ struct nlattr *tb[P4TC_META_MAX + 1];
+ char *m_name;
+
+ ret = nla_parse_nested(tb, P4TC_META_MAX, nla, p4tc_meta_policy,
+ extack);
+ if (ret < 0)
+ goto out;
+
+ if (tcf_meta_find_byname_attr(tb[P4TC_META_NAME], pipeline) ||
+ tcf_meta_find_byid(pipeline, m_id)) {
+ NL_SET_ERR_MSG(extack, "Metadatum already exists");
+ ret = -EEXIST;
+ goto out;
+ }
+
+ if (tb[P4TC_META_NAME]) {
+ m_name = nla_data(tb[P4TC_META_NAME]);
+ } else {
+ NL_SET_ERR_MSG(extack, "Must specify metadatum name");
+ ret = -ENOENT;
+ goto out;
+ }
+
+ if (tb[P4TC_META_SIZE]) {
+ sz_params = nla_data(tb[P4TC_META_SIZE]);
+ } else {
+ NL_SET_ERR_MSG(extack, "Must specify metadatum size params");
+ ret = -ENOENT;
+ goto out;
+ }
+
+ return __tcf_meta_create(pipeline, m_id, m_name, sz_params, GFP_KERNEL,
+ false, extack);
+
+out:
+ return ERR_PTR(ret);
+}
+
+static struct p4tc_metadata *tcf_meta_update(struct nlmsghdr *n,
+ struct nlattr *nla, u32 m_id,
+ struct p4tc_pipeline *pipeline,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[P4TC_META_MAX + 1];
+ struct p4tc_metadata *meta;
+ int ret;
+
+ ret = nla_parse_nested(tb, P4TC_META_MAX, nla, p4tc_meta_policy,
+ extack);
+
+ if (ret < 0)
+ goto out;
+
+ meta = tcf_meta_find_byanyattr(pipeline, tb[P4TC_META_NAME], m_id,
+ extack);
+ if (IS_ERR(meta))
+ return meta;
+
+ if (tb[P4TC_META_SIZE]) {
+ struct p4tc_type *new_datatype, *curr_datatype;
+ struct p4tc_meta_size_params *sz_params;
+ u32 new_bytesz, curr_bytesz;
+ int new_bitsz;
+ u32 p_meta_offset;
+ int diff;
+
+ sz_params = nla_data(tb[P4TC_META_SIZE]);
+ new_datatype = p4type_find_byid(sz_params->datatype);
+ if (!new_datatype) {
+ NL_SET_ERR_MSG(extack, "Invalid data type");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ new_bitsz =
+ p4tc_check_meta_size(sz_params, new_datatype, extack);
+ if (new_bitsz < 0) {
+ ret = new_bitsz;
+ goto out;
+ }
+
+ new_bytesz = BITS_TO_U32(new_datatype->bitsz) * sizeof(u32);
+
+ curr_datatype = p4type_find_byid(meta->m_datatype);
+ curr_bytesz = BITS_TO_U32(curr_datatype->bitsz) * sizeof(u32);
+
+ diff = new_bytesz - curr_bytesz;
+ p_meta_offset = pipeline->p_meta_offset + diff;
+ if (p_meta_offset > BITS_TO_BYTES(P4TC_MAXMETA_OFFSET)) {
+ NL_SET_ERR_MSG(extack, "Metadata max offset exceeded");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ pipeline->p_meta_offset = p_meta_offset;
+
+ meta->m_datatype = new_datatype->typeid;
+ meta->m_startbit = sz_params->startbit;
+ meta->m_endbit = sz_params->endbit;
+ meta->m_sz = new_bitsz;
+ }
+
+ return meta;
+
+out:
+ return ERR_PTR(ret);
+}
+
+static struct p4tc_template_common *
+tcf_meta_cu(struct net *net, struct nlmsghdr *n, struct nlattr *nla,
+ struct p4tc_nl_pname *nl_pname, u32 *ids,
+ struct netlink_ext_ack *extack)
+{
+ u32 pipeid = ids[P4TC_PID_IDX], m_id = ids[P4TC_MID_IDX];
+ struct p4tc_pipeline *pipeline;
+ struct p4tc_metadata *meta;
+
+ pipeline = tcf_pipeline_find_byany_unsealed(net, nl_pname->data, pipeid,
+ extack);
+ if (IS_ERR(pipeline))
+ return (void *)pipeline;
+
+ if (n->nlmsg_flags & NLM_F_REPLACE)
+ meta = tcf_meta_update(n, nla, m_id, pipeline, extack);
+ else
+ meta = tcf_meta_create(n, nla, m_id, pipeline, extack);
+
+ if (IS_ERR(meta))
+ goto out;
+
+ if (!nl_pname->passed)
+ strscpy(nl_pname->data, pipeline->common.name, PIPELINENAMSIZ);
+
+ if (!ids[P4TC_PID_IDX])
+ ids[P4TC_PID_IDX] = pipeline->common.p_id;
+
+out:
+ return (struct p4tc_template_common *)meta;
+}
+
+static int _tcf_meta_fill_nlmsg(struct sk_buff *skb,
+ const struct p4tc_metadata *meta)
+{
+ unsigned char *b = nlmsg_get_pos(skb);
+ struct p4tc_meta_size_params sz_params;
+ struct nlattr *nest;
+
+ if (nla_put_u32(skb, P4TC_PATH, meta->m_id))
+ goto out_nlmsg_trim;
+
+ nest = nla_nest_start(skb, P4TC_PARAMS);
+ if (!nest)
+ goto out_nlmsg_trim;
+
+ sz_params.datatype = meta->m_datatype;
+ sz_params.startbit = meta->m_startbit;
+ sz_params.endbit = meta->m_endbit;
+
+ if (nla_put_string(skb, P4TC_META_NAME, meta->common.name))
+ goto out_nlmsg_trim;
+ if (nla_put(skb, P4TC_META_SIZE, sizeof(sz_params), &sz_params))
+ goto out_nlmsg_trim;
+
+ nla_nest_end(skb, nest);
+
+ return skb->len;
+
+out_nlmsg_trim:
+ nlmsg_trim(skb, b);
+ return -1;
+}
+
+static int tcf_meta_fill_nlmsg(struct net *net, struct sk_buff *skb,
+ struct p4tc_template_common *template,
+ struct netlink_ext_ack *extack)
+{
+ const struct p4tc_metadata *meta = to_meta(template);
+
+ if (_tcf_meta_fill_nlmsg(skb, meta) <= 0) {
+ NL_SET_ERR_MSG(extack,
+ "Failed to fill notification attributes for metadatum");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tcf_meta_flush(struct sk_buff *skb, struct p4tc_pipeline *pipeline,
+ struct netlink_ext_ack *extack)
+{
+ struct p4tc_metadata *meta;
+ unsigned long tmp, m_id;
+ unsigned char *b = nlmsg_get_pos(skb);
+ int ret = 0;
+ int i = 0;
+
+ if (nla_put_u32(skb, P4TC_PATH, 0))
+ goto out_nlmsg_trim;
+
+ if (idr_is_empty(&pipeline->p_meta_idr)) {
+ NL_SET_ERR_MSG(extack, "There is not metadata to flush");
+ ret = 0;
+ goto out_nlmsg_trim;
+ }
+
+ idr_for_each_entry_ul(&pipeline->p_meta_idr, meta, tmp, m_id) {
+ if (_tcf_meta_put(pipeline, meta, false, extack) < 0) {
+ ret = -EBUSY;
+ continue;
+ }
+ i++;
+ }
+
+ nla_put_u32(skb, P4TC_COUNT, i);
+
+ if (ret < 0) {
+ if (i == 0) {
+ NL_SET_ERR_MSG(extack, "Unable to flush any metadata");
+ goto out_nlmsg_trim;
+ } else {
+ NL_SET_ERR_MSG(extack, "Unable to flush all metadata");
+ }
+ }
+
+ return i;
+
+out_nlmsg_trim:
+ nlmsg_trim(skb, b);
+ return ret;
+}
+
+static int tcf_meta_gd(struct net *net, struct sk_buff *skb, struct nlmsghdr *n,
+ struct nlattr *nla, struct p4tc_nl_pname *nl_pname,
+ u32 *ids, struct netlink_ext_ack *extack)
+{
+ u32 pipeid = ids[P4TC_PID_IDX], m_id = ids[P4TC_MID_IDX];
+ struct nlattr *tb[P4TC_META_MAX + 1] = {};
+ unsigned char *b = nlmsg_get_pos(skb);
+ int ret = 0;
+ struct p4tc_pipeline *pipeline;
+ struct p4tc_metadata *meta;
+
+ if (n->nlmsg_type == RTM_DELP4TEMPLATE)
+ pipeline = tcf_pipeline_find_byany_unsealed(net, nl_pname->data,
+ pipeid, extack);
+ else
+ pipeline = tcf_pipeline_find_byany(net, nl_pname->data, pipeid,
+ extack);
+ if (IS_ERR(pipeline))
+ return PTR_ERR(pipeline);
+
+ if (nla) {
+ ret = nla_parse_nested(tb, P4TC_META_MAX, nla, p4tc_meta_policy,
+ extack);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ if (!nl_pname->passed)
+ strscpy(nl_pname->data, pipeline->common.name, PIPELINENAMSIZ);
+
+ if (!ids[P4TC_PID_IDX])
+ ids[P4TC_PID_IDX] = pipeline->common.p_id;
+
+ if (n->nlmsg_type == RTM_DELP4TEMPLATE && (n->nlmsg_flags & NLM_F_ROOT))
+ return tcf_meta_flush(skb, pipeline, extack);
+
+ meta = tcf_meta_find_byanyattr(pipeline, tb[P4TC_META_NAME], m_id,
+ extack);
+ if (IS_ERR(meta))
+ return PTR_ERR(meta);
+
+ if (_tcf_meta_fill_nlmsg(skb, meta) < 0) {
+ NL_SET_ERR_MSG(extack,
+ "Failed to fill notification attributes for metadatum");
+ return -EINVAL;
+ }
+
+ if (n->nlmsg_type == RTM_DELP4TEMPLATE) {
+ ret = _tcf_meta_put(pipeline, meta, false, extack);
+ if (ret < 0) {
+ NL_SET_ERR_MSG(extack,
+ "Unable to delete referenced metadatum");
+ goto out_nlmsg_trim;
+ }
+ }
+
+ return ret;
+
+out_nlmsg_trim:
+ nlmsg_trim(skb, b);
+ return ret;
+}
+
+static int tcf_meta_dump(struct sk_buff *skb, struct p4tc_dump_ctx *ctx,
+ struct nlattr *nla, char **p_name, u32 *ids,
+ struct netlink_ext_ack *extack)
+{
+ unsigned char *b = nlmsg_get_pos(skb);
+ const u32 pipeid = ids[P4TC_PID_IDX];
+ struct net *net = sock_net(skb->sk);
+ unsigned long m_id = 0;
+ int i = 0;
+ struct p4tc_pipeline *pipeline;
+ struct p4tc_metadata *meta;
+ unsigned long tmp;
+
+ if (!ctx->ids[P4TC_PID_IDX]) {
+ pipeline =
+ tcf_pipeline_find_byany(net, *p_name, pipeid, extack);
+ if (IS_ERR(pipeline))
+ return PTR_ERR(pipeline);
+ ctx->ids[P4TC_PID_IDX] = pipeline->common.p_id;
+ } else {
+ pipeline = tcf_pipeline_find_byid(net, ctx->ids[P4TC_PID_IDX]);
+ }
+
+ m_id = ctx->ids[P4TC_MID_IDX];
+
+ idr_for_each_entry_continue_ul(&pipeline->p_meta_idr, meta, tmp, m_id) {
+ struct nlattr *count, *param;
+
+ if (i == P4TC_MSGBATCH_SIZE)
+ break;
+
+ count = nla_nest_start(skb, i + 1);
+ if (!count)
+ goto out_nlmsg_trim;
+
+ param = nla_nest_start(skb, P4TC_PARAMS);
+ if (!param)
+ goto out_nlmsg_trim;
+ if (nla_put_string(skb, P4TC_META_NAME, meta->common.name))
+ goto out_nlmsg_trim;
+
+ nla_nest_end(skb, param);
+ nla_nest_end(skb, count);
+
+ i++;
+ }
+
+ if (i == 0) {
+ if (!ctx->ids[P4TC_MID_IDX])
+ NL_SET_ERR_MSG(extack, "There is no metadata to dump");
+ return 0;
+ }
+
+ if (!ids[P4TC_PID_IDX])
+ ids[P4TC_PID_IDX] = pipeline->common.p_id;
+
+ if (!(*p_name))
+ *p_name = pipeline->common.name;
+
+ ctx->ids[P4TC_MID_IDX] = m_id;
+
+ return skb->len;
+
+out_nlmsg_trim:
+ nlmsg_trim(skb, b);
+ return -ENOMEM;
+}
+
+static int __p4tc_register_kmeta(struct p4tc_pipeline *pipeline, u32 m_id,
+ const char *m_name, u8 startbit, u8 endbit,
+ bool read_only, u32 datatype)
+{
+ struct p4tc_meta_size_params sz_params = {
+ .startbit = startbit,
+ .endbit = endbit,
+ .datatype = datatype,
+ };
+ struct p4tc_metadata *meta;
+
+ meta = __tcf_meta_create(pipeline, m_id, m_name, &sz_params, GFP_ATOMIC,
+ read_only, NULL);
+ if (IS_ERR(meta)) {
+ pr_err("Failed to register metadata %s %ld\n", m_name,
+ PTR_ERR(meta));
+ return PTR_ERR(meta);
+ }
+
+ pr_debug("Registered kernel metadata %s with id %u\n", m_name, m_id);
+
+ return 0;
+}
+
+#define p4tc_register_kmeta(...) \
+ do { \
+ if (__p4tc_register_kmeta(__VA_ARGS__) < 0) \
+ return; \
+ } while (0)
+
+void tcf_meta_init(struct p4tc_pipeline *root_pipe)
+{
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_PKTLEN, "pktlen", 0, 31,
+ false, P4T_U32);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_DATALEN, "datalen", 0,
+ 31, false, P4T_U32);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_SKBMARK, "skbmark", 0,
+ 31, false, P4T_U32);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_TCINDEX, "tcindex", 0,
+ 15, false, P4T_U16);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_SKBHASH, "skbhash", 0,
+ 31, false, P4T_U32);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_SKBPRIO, "skbprio", 0,
+ 31, false, P4T_U32);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_IFINDEX, "ifindex", 0,
+ 31, false, P4T_S32);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_SKBIIF, "iif", 0, 31,
+ true, P4T_DEV);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_PROTOCOL, "skbproto", 0,
+ 15, false, P4T_BE16);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_PTYPEOFF, "ptypeoff", 0,
+ 7, false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_CLONEOFF, "cloneoff", 0,
+ 7, false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_PTCLNOFF, "ptclnoff", 0,
+ 15, false, P4T_U16);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_QMAP, "skbqmap", 0, 15,
+ false, P4T_U16);
+
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_PKTYPE, "skbptype", 0,
+ 2, false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_IDF, "skbidf", 3, 3,
+ false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_IPSUM, "skbipsum", 5, 6,
+ false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_OOOK, "skboook", 7, 7,
+ false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_FCLONE, "fclone", 2, 3,
+ false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_PEEKED, "skbpeek", 4, 4,
+ false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_DIRECTION, "direction",
+ 7, 7, false, P4T_U8);
+#elif define(__BIG_ENDIAN_BITFIELD)
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_PKTYPE, "skbptype", 5,
+ 7, false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_IDF, "skbidf", 4, 4,
+ false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_IPSUM, "skbipsum", 1, 2,
+ false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_OOOK, "skboook", 0, 0,
+ false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_FCLONE, "fclone", 4, 5,
+ false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_PEEKED, "skbpeek", 3, 3,
+ false, P4T_U8);
+
+ p4tc_register_kmeta(root_pipe, P4TC_KERNEL_META_DIRECTION, "direction",
+ 0, 0, false, P4T_U8);
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+}
+
+const struct p4tc_template_ops p4tc_meta_ops = {
+ .cu = tcf_meta_cu,
+ .fill_nlmsg = tcf_meta_fill_nlmsg,
+ .gd = tcf_meta_gd,
+ .put = tcf_meta_put,
+ .dump = tcf_meta_dump,
+};
@@ -80,6 +80,8 @@ static const struct nla_policy tc_pipeline_policy[P4TC_PIPELINE_MAX + 1] = {
static void tcf_pipeline_destroy(struct p4tc_pipeline *pipeline,
bool free_pipeline)
{
+ idr_destroy(&pipeline->p_meta_idr);
+
if (free_pipeline)
kfree(pipeline);
}
@@ -104,6 +106,8 @@ static int tcf_pipeline_put(struct net *net,
struct p4tc_pipeline_net *pipe_net = net_generic(net, pipeline_net_id);
struct p4tc_pipeline *pipeline = to_pipeline(template);
struct net *pipeline_net = maybe_get_net(net);
+ struct p4tc_metadata *meta;
+ unsigned long m_id, tmp;
if (pipeline_net && !refcount_dec_if_one(&pipeline->p_ref)) {
NL_SET_ERR_MSG(extack, "Can't delete referenced pipeline");
@@ -112,6 +116,9 @@ static int tcf_pipeline_put(struct net *net,
idr_remove(&pipe_net->pipeline_idr, pipeline->common.p_id);
+ idr_for_each_entry_ul(&pipeline->p_meta_idr, meta, tmp, m_id)
+ meta->common.ops->put(net, &meta->common, true, extack);
+
/* XXX: The action fields are only accessed in the control path
* since they will be copied to the filter, where the data path
* will use them. So there is no need to free them in the rcu
@@ -154,11 +161,6 @@ static inline int pipeline_try_set_state_ready(struct p4tc_pipeline *pipeline,
return true;
}
-static inline bool pipeline_sealed(struct p4tc_pipeline *pipeline)
-{
- return pipeline->p_state == P4TC_STATE_READY;
-}
-
static int p4tc_action_init(struct net *net, struct nlattr *nla,
struct tc_action *acts[], u32 pipeid, u32 flags,
struct netlink_ext_ack *extack)
@@ -317,6 +319,9 @@ static struct p4tc_pipeline *tcf_pipeline_create(struct net *net,
pipeline->num_postacts = 0;
}
+ idr_init(&pipeline->p_meta_idr);
+ pipeline->p_meta_offset = 0;
+
pipeline->p_state = P4TC_STATE_NOT_READY;
pipeline->net = net;
@@ -508,6 +513,7 @@ tcf_pipeline_update(struct net *net, struct nlmsghdr *n, struct nlattr *nla,
ret = pipeline_try_set_state_ready(pipeline, extack);
if (ret < 0)
goto postactions_destroy;
+ tcf_meta_fill_user_offsets(pipeline);
}
if (max_rules)
@@ -724,12 +730,16 @@ static void __tcf_pipeline_init(void)
strscpy(root_pipeline->common.name, "kernel", PIPELINENAMSIZ);
+ idr_init(&root_pipeline->p_meta_idr);
+
root_pipeline->common.ops =
(struct p4tc_template_ops *)&p4tc_pipeline_ops;
root_pipeline->common.p_id = pipeid;
root_pipeline->p_state = P4TC_STATE_READY;
+
+ tcf_meta_init(root_pipeline);
}
static void tcf_pipeline_init(void)
@@ -42,6 +42,7 @@ static bool obj_is_valid(u32 obj)
{
switch (obj) {
case P4TC_OBJ_PIPELINE:
+ case P4TC_OBJ_META:
return true;
default:
return false;
@@ -50,6 +51,7 @@ static bool obj_is_valid(u32 obj)
static const struct p4tc_template_ops *p4tc_ops[P4TC_OBJ_MAX] = {
[P4TC_OBJ_PIPELINE] = &p4tc_pipeline_ops,
+ [P4TC_OBJ_META] = &p4tc_meta_ops,
};
int tcf_p4_tmpl_generic_dump(struct sk_buff *skb, struct p4tc_dump_ctx *ctx,
@@ -125,11 +127,15 @@ static int tc_ctl_p4_tmpl_gd_1(struct net *net, struct sk_buff *skb,
ids[P4TC_PID_IDX] = t->pipeid;
if (tb[P4TC_PATH]) {
+ const u32 *arg_ids = nla_data(tb[P4TC_PATH]);
+
if ((nla_len(tb[P4TC_PATH])) >
(P4TC_PATH_MAX - 1) * sizeof(u32)) {
NL_SET_ERR_MSG(extack, "Path is too big");
return -E2BIG;
}
+
+ memcpy(&ids[P4TC_MID_IDX], arg_ids, nla_len(tb[P4TC_PATH]));
}
op = (struct p4tc_template_ops *)p4tc_ops[t->obj];
@@ -309,12 +315,17 @@ tcf_p4_tmpl_cu_1(struct sk_buff *skb, struct net *net, struct nlmsghdr *n,
ids[P4TC_PID_IDX] = t->pipeid;
if (p4tc_attr[P4TC_PATH]) {
+ const u32 *arg_ids = nla_data(p4tc_attr[P4TC_PATH]);
+
if ((nla_len(p4tc_attr[P4TC_PATH])) >
(P4TC_PATH_MAX - 1) * sizeof(u32)) {
NL_SET_ERR_MSG(extack, "Path is too big");
ret = -E2BIG;
goto out;
}
+
+ memcpy(&ids[P4TC_MID_IDX], arg_ids,
+ nla_len(p4tc_attr[P4TC_PATH]));
}
op = (struct p4tc_template_ops *)p4tc_ops[t->obj];
@@ -504,11 +515,15 @@ static int tc_ctl_p4_tmpl_dump_1(struct sk_buff *skb, struct nlattr *arg,
ids[P4TC_PID_IDX] = t->pipeid;
if (tb[P4TC_PATH]) {
+ const u32 *arg_ids = nla_data(tb[P4TC_PATH]);
+
if ((nla_len(tb[P4TC_PATH])) >
(P4TC_PATH_MAX - 1) * sizeof(u32)) {
NL_SET_ERR_MSG(extack, "Path is too big");
return -E2BIG;
}
+
+ memcpy(&ids[P4TC_MID_IDX], arg_ids, nla_len(tb[P4TC_PATH]));
}
op = (struct p4tc_template_ops *)p4tc_ops[t->obj];