diff mbox series

[RFC,v2,net-next,11/28] p4tc: add metadata create, update, delete, get, flush and dump

Message ID 20230517110232.29349-11-jhs@mojatatu.com (mailing list archive)
State RFC
Delegated to: Netdev Maintainers
Headers show
Series Introducing P4TC | expand

Checks

Context Check Description
netdev/series_format fail Series longer than 15 patches (and no cover letter)
netdev/tree_selection success Clearly marked for net-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit fail Errors and warnings before: 29 this patch: 31
netdev/cc_maintainers warning 4 maintainers not CCed: victor@mojatatu.com gor@linux.ibm.com pctammela@mojatatu.com sumanthk@linux.ibm.com
netdev/build_clang fail Errors and warnings before: 8 this patch: 10
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn fail Errors and warnings before: 29 this patch: 31
netdev/checkpatch warning CHECK: Please use a blank line after function/struct/union/enum declarations WARNING: Macros with flow control statements should be avoided WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline warning Was 1 now: 1

Commit Message

Jamal Hadi Salim May 17, 2023, 11:02 a.m. UTC
This commit allows users to create, update, delete and get a P4 pipeline's
metadatum. It also allows users to flush and dump all of the P4 pipeline's
metadata.

As an example, if one were to create a metadatum named mname in a pipeline
named ptables with a type of 8 bits, one would use the following command:

tc p4template create metadata/ptables/mname [mid 1] type bit8

Note that, in the above command, the metadatum id is optional. If one
does not specify a metadatum id, the kernel will assign one.

If one were to update a metadatum named mname in a pipeline named ptables
with an mid of 1, one would use the following command:

tc p4template update metadata/ptables/[mname] [mid 1] type bit4

Note that, in the above command, the metadatum's id and the metadatum's
name are optional. That is, one may specify only the metadatum's name,
only the metadatum's id, or both.

If one were to delete a metadatum named mname from a pipeline named
ptables with an mid of 1, one would use the following command:

tc p4template del metadata/ptables/[mname] [mid 1]

Note that, in the above command, the metadatum's id and the metadatum's
name are optional. That is, one may specify only the metadatum's name,
only the metadatum's id, or both.

If one were to flush all the metadata from a pipeline named ptables, one
would use the following command:

tc p4template del metadata/ptables/

If one were to get a metadatum named mname from a pipeline named ptables
with an mid of 1, one would use the following command:

tc p4template get metadata/ptables/[mname] [mid 1]

Note that, in the above command, the metadatum's id and the metadatum's
name are optional. That is, one may specify only the metadatum's name,
only the metadatum's id, or both.

If one were to dump all the metadata from a pipeline named ptables, one
would use the following command:

tc p4template get metadata/ptables/

Co-developed-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Victor Nogueira <victor@mojatatu.com>
Co-developed-by: Pedro Tammela <pctammela@mojatatu.com>
Signed-off-by: Pedro Tammela <pctammela@mojatatu.com>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
---
 include/net/p4tc.h             |  44 ++
 include/uapi/linux/p4tc.h      |  51 ++
 net/sched/p4tc/Makefile        |   2 +-
 net/sched/p4tc/p4tc_meta.c     | 819 +++++++++++++++++++++++++++++++++
 net/sched/p4tc/p4tc_pipeline.c |  23 +-
 net/sched/p4tc/p4tc_tmpl_api.c |  19 +
 6 files changed, 952 insertions(+), 6 deletions(-)
 create mode 100644 net/sched/p4tc/p4tc_meta.c
diff mbox series

Patch

diff --git a/include/net/p4tc.h b/include/net/p4tc.h
index 178bbdf68719..6e3c4681c1d6 100644
--- a/include/net/p4tc.h
+++ b/include/net/p4tc.h
@@ -12,11 +12,23 @@ 
 
 #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_percpu_scratchpad {
+	u32 keysz;
+	u32 maskid;
+	u8 key[BITS_TO_BYTES(P4TC_MAX_KEYSZ)];
+	u8 hdrs[BITS_TO_BYTES(HEADER_MAX_LEN)];
+	u8 metadata[BITS_TO_BYTES(META_MAX_LEN)];
+};
+
+DECLARE_PER_CPU(struct p4tc_percpu_scratchpad, p4tc_percpu_scratchpad);
 
 struct p4tc_dump_ctx {
 	u32 ids[P4TC_PATH_MAX];
@@ -78,6 +90,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 +98,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 +140,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
diff --git a/include/uapi/linux/p4tc.h b/include/uapi/linux/p4tc.h
index 739c0fe18e95..8934c032dc87 100644
--- a/include/uapi/linux/p4tc.h
+++ b/include/uapi/linux/p4tc.h
@@ -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))))
 
diff --git a/net/sched/p4tc/Makefile b/net/sched/p4tc/Makefile
index 0881a7563646..d523e668cbe5 100644
--- a/net/sched/p4tc/Makefile
+++ b/net/sched/p4tc/Makefile
@@ -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
diff --git a/net/sched/p4tc/p4tc_meta.c b/net/sched/p4tc/p4tc_meta.c
new file mode 100644
index 000000000000..21a5477ab0c6
--- /dev/null
+++ b/net/sched/p4tc/p4tc_meta.c
@@ -0,0 +1,819 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * net/sched/p4tc_meta.c	P4 TC API METADATA
+ *
+ * Copyright (c) 2022-2023, Mojatatu Networks
+ * Copyright (c) 2022-2023, 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 defined(__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,
+};
diff --git a/net/sched/p4tc/p4tc_pipeline.c b/net/sched/p4tc/p4tc_pipeline.c
index 776a8d37b615..56b4f7be8d17 100644
--- a/net/sched/p4tc/p4tc_pipeline.c
+++ b/net/sched/p4tc/p4tc_pipeline.c
@@ -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;
@@ -510,6 +515,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)
@@ -726,12 +732,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)
@@ -745,6 +755,9 @@  static void tcf_pipeline_init(void)
 	__tcf_pipeline_init();
 }
 
+DEFINE_PER_CPU(struct p4tc_percpu_scratchpad, p4tc_percpu_scratchpad);
+EXPORT_PER_CPU_SYMBOL_GPL(p4tc_percpu_scratchpad);
+
 const struct p4tc_template_ops p4tc_pipeline_ops = {
 	.init = tcf_pipeline_init,
 	.cu = tcf_pipeline_cu,
diff --git a/net/sched/p4tc/p4tc_tmpl_api.c b/net/sched/p4tc/p4tc_tmpl_api.c
index a936ec84841d..19dd6f41a3a4 100644
--- a/net/sched/p4tc/p4tc_tmpl_api.c
+++ b/net/sched/p4tc/p4tc_tmpl_api.c
@@ -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,
@@ -124,6 +126,12 @@  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]);
+
+		memcpy(&ids[P4TC_MID_IDX], arg_ids, nla_len(tb[P4TC_PATH]));
+	}
+
 	op = (struct p4tc_template_ops *)p4tc_ops[t->obj];
 
 	ret = op->gd(net, skb, n, tb[P4TC_PARAMS], nl_pname, ids, extack);
@@ -300,6 +308,12 @@  tcf_p4_tmpl_cu_1(struct sk_buff *skb, struct net *net, struct nlmsghdr *n,
 
 	ids[P4TC_PID_IDX] = t->pipeid;
 
+	if (tb[P4TC_PATH]) {
+		const u32 *arg_ids = nla_data(tb[P4TC_PATH]);
+
+		memcpy(&ids[P4TC_MID_IDX], arg_ids, nla_len(tb[P4TC_PATH]));
+	}
+
 	op = (struct p4tc_template_ops *)p4tc_ops[t->obj];
 	tmpl = op->cu(net, n, tb[P4TC_PARAMS], nl_pname, ids, extack);
 	if (IS_ERR(tmpl))
@@ -486,6 +500,11 @@  static int tc_ctl_p4_tmpl_dump_1(struct sk_buff *skb, struct nlattr *arg,
 	root = nla_nest_start(skb, P4TC_ROOT);
 
 	ids[P4TC_PID_IDX] = t->pipeid;
+	if (tb[P4TC_PATH]) {
+		const u32 *arg_ids = nla_data(tb[P4TC_PATH]);
+
+		memcpy(&ids[P4TC_MID_IDX], arg_ids, nla_len(tb[P4TC_PATH]));
+	}
 
 	op = (struct p4tc_template_ops *)p4tc_ops[t->obj];
 	ret = op->dump(skb, ctx, tb[P4TC_PARAMS], &p_name, ids, extack);