new file mode 100644
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __NET_P4TC_H
+#define __NET_P4TC_H
+
+#include <uapi/linux/p4tc.h>
+#include <linux/workqueue.h>
+#include <net/sch_generic.h>
+#include <net/net_namespace.h>
+#include <linux/refcount.h>
+#include <linux/rhashtable.h>
+#include <linux/rhashtable-types.h>
+
+#define P4TC_PATH_MAX 3
+
+struct p4tc_dump_ctx {
+ u32 ids[P4TC_PATH_MAX];
+};
+
+struct p4tc_template_common;
+
+struct p4tc_template_ops {
+ void (*init)(void);
+ struct p4tc_template_common *(*cu)(struct net *net, struct nlmsghdr *n,
+ struct nlattr *nla,
+ struct netlink_ext_ack *extack);
+ int (*put)(struct p4tc_template_common *tmpl,
+ struct netlink_ext_ack *extack);
+ int (*gd)(struct net *net, struct sk_buff *skb, struct nlmsghdr *n,
+ struct nlattr *nla, struct netlink_ext_ack *extack);
+ int (*fill_nlmsg)(struct net *net, struct sk_buff *skb,
+ struct p4tc_template_common *tmpl,
+ struct netlink_ext_ack *extack);
+ int (*dump)(struct sk_buff *skb, struct p4tc_dump_ctx *ctx,
+ struct nlattr *nla, u32 *ids,
+ struct netlink_ext_ack *extack);
+ int (*dump_1)(struct sk_buff *skb, struct p4tc_template_common *common);
+};
+
+struct p4tc_template_common {
+ char name[P4TC_TMPL_NAMSZ];
+ struct p4tc_template_ops *ops;
+};
+
+static inline bool p4tc_tmpl_msg_is_update(struct nlmsghdr *n)
+{
+ return n->nlmsg_type == RTM_UPDATEP4TEMPLATE;
+}
+
+int p4tc_tmpl_generic_dump(struct sk_buff *skb, struct p4tc_dump_ctx *ctx,
+ struct idr *idr, int idx,
+ struct netlink_ext_ack *extack);
+
+#endif
@@ -2,8 +2,47 @@
#ifndef __LINUX_P4TC_H
#define __LINUX_P4TC_H
+#include <linux/types.h>
+#include <linux/pkt_sched.h>
+
+/* pipeline header */
+struct p4tcmsg {
+ __u32 obj;
+};
+
+#define P4TC_MSGBATCH_SIZE 16
+
#define P4TC_MAX_KEYSZ 512
+#define P4TC_TMPL_NAMSZ 32
+
+/* Root attributes */
+enum {
+ P4TC_ROOT_UNSPEC,
+ P4TC_ROOT, /* nested messages */
+ __P4TC_ROOT_MAX,
+};
+
+#define P4TC_ROOT_MAX (__P4TC_ROOT_MAX - 1)
+
+/* P4 Object types */
+enum {
+ P4TC_OBJ_UNSPEC,
+ __P4TC_OBJ_MAX,
+};
+
+#define P4TC_OBJ_MAX (__P4TC_OBJ_MAX - 1)
+
+/* P4 attributes */
+enum {
+ P4TC_UNSPEC,
+ P4TC_PATH,
+ P4TC_PARAMS,
+ __P4TC_MAX,
+};
+
+#define P4TC_MAX (__P4TC_MAX - 1)
+
enum {
P4TC_T_UNSPEC,
P4TC_T_U8,
@@ -30,4 +69,7 @@ enum {
#define P4TC_T_MAX (__P4TC_T_MAX - 1)
+#define P4TC_RTA(r) \
+ ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct p4tcmsg))))
+
#endif
@@ -194,6 +194,15 @@ enum {
RTM_GETTUNNEL,
#define RTM_GETTUNNEL RTM_GETTUNNEL
+ RTM_CREATEP4TEMPLATE = 124,
+#define RTM_CREATEP4TEMPLATE RTM_CREATEP4TEMPLATE
+ RTM_DELP4TEMPLATE,
+#define RTM_DELP4TEMPLATE RTM_DELP4TEMPLATE
+ RTM_GETP4TEMPLATE,
+#define RTM_GETP4TEMPLATE RTM_GETP4TEMPLATE
+ RTM_UPDATEP4TEMPLATE,
+#define RTM_UPDATEP4TEMPLATE RTM_UPDATEP4TEMPLATE
+
__RTM_MAX,
#define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1)
};
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
-obj-y := p4tc_types.o
+obj-y := p4tc_types.o p4tc_tmpl_api.o
new file mode 100644
@@ -0,0 +1,499 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * net/sched/p4tc/p4tc_tmpl_api.c P4 TC TEMPLATE API
+ *
+ * Copyright (c) 2022-2024, Mojatatu Networks
+ * Copyright (c) 2022-2024, Intel Corporation.
+ * Authors: Jamal Hadi Salim <jhs@mojatatu.com>
+ * Victor Nogueira <victor@mojatatu.com>
+ * Pedro Tammela <pctammela@mojatatu.com>
+ */
+
+#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>
+
+static const struct nla_policy p4tc_root_policy[P4TC_ROOT_MAX + 1] = {
+ [P4TC_ROOT] = { .type = NLA_NESTED },
+};
+
+static const struct nla_policy p4tc_policy[P4TC_MAX + 1] = {
+ [P4TC_PATH] = { .type = NLA_BINARY,
+ .len = P4TC_PATH_MAX * sizeof(u32) },
+ [P4TC_PARAMS] = { .type = NLA_NESTED },
+};
+
+static bool obj_is_valid(u32 obj)
+{
+ switch (obj) {
+ default:
+ return false;
+ }
+}
+
+static const struct p4tc_template_ops *p4tc_ops[P4TC_OBJ_MAX + 1] = {
+};
+
+int p4tc_tmpl_generic_dump(struct sk_buff *skb, struct p4tc_dump_ctx *ctx,
+ struct idr *idr, int idx,
+ struct netlink_ext_ack *extack)
+{
+ unsigned char *b = nlmsg_get_pos(skb);
+ struct p4tc_template_common *common;
+ unsigned long id = 0;
+ unsigned long tmp;
+ int i = 0;
+
+ id = ctx->ids[idx];
+
+ idr_for_each_entry_continue_ul(idr, common, tmp, id) {
+ struct nlattr *count;
+ int ret;
+
+ if (i == P4TC_MSGBATCH_SIZE)
+ break;
+
+ count = nla_nest_start(skb, i + 1);
+ if (!count)
+ goto out_nlmsg_trim;
+ ret = common->ops->dump_1(skb, common);
+ if (ret < 0) {
+ goto out_nlmsg_trim;
+ } else if (ret) {
+ nla_nest_cancel(skb, count);
+ continue;
+ }
+ nla_nest_end(skb, count);
+
+ i++;
+ }
+
+ if (i == 0) {
+ if (!ctx->ids[idx])
+ NL_SET_ERR_MSG(extack,
+ "There are no pipeline components");
+ return 0;
+ }
+
+ ctx->ids[idx] = id;
+
+ return skb->len;
+
+out_nlmsg_trim:
+ nlmsg_trim(skb, b);
+ return -ENOMEM;
+}
+
+static int tc_ctl_p4_tmpl_gd_1(struct net *net, struct sk_buff *skb,
+ struct nlmsghdr *n, struct nlattr *arg,
+ struct netlink_ext_ack *extack)
+{
+ struct p4tcmsg *t = (struct p4tcmsg *)nlmsg_data(n);
+ struct p4tc_template_ops *obj_op;
+ struct nlattr *tb[P4TC_MAX + 1];
+ int ret;
+
+ /* All checks will fail at this point because obj_is_valid will return
+ * false. The next patch will make this functional
+ */
+ if (!obj_is_valid(t->obj)) {
+ NL_SET_ERR_MSG(extack, "Invalid object type");
+ return -EINVAL;
+ }
+
+ ret = nla_parse_nested(tb, P4TC_MAX, arg, p4tc_policy, extack);
+ if (ret < 0)
+ return ret;
+
+ obj_op = (struct p4tc_template_ops *)p4tc_ops[t->obj];
+
+ ret = obj_op->gd(net, skb, n, tb[P4TC_PARAMS], extack);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+static int tc_ctl_p4_tmpl_gd_n(struct sk_buff *skb, struct nlmsghdr *n,
+ struct nlattr *nla, int event,
+ struct netlink_ext_ack *extack)
+{
+ struct p4tcmsg *t = (struct p4tcmsg *)nlmsg_data(n);
+ struct nlattr *tb[P4TC_MSGBATCH_SIZE + 1];
+ struct net *net = sock_net(skb->sk);
+ u32 portid = NETLINK_CB(skb).portid;
+ struct p4tcmsg *t_new;
+ struct sk_buff *nskb;
+ struct nlmsghdr *nlh;
+ struct nlattr *root;
+ int ret = 0;
+ int i;
+
+ ret = nla_parse_nested(tb, P4TC_MSGBATCH_SIZE, nla, NULL, extack);
+ if (ret < 0)
+ return ret;
+
+ nskb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!nskb)
+ return -ENOMEM;
+
+ nlh = nlmsg_put(nskb, portid, n->nlmsg_seq, event, sizeof(*t),
+ n->nlmsg_flags);
+ if (!nlh) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ t_new = nlmsg_data(nlh);
+ t_new->obj = t->obj;
+
+ root = nla_nest_start(nskb, P4TC_ROOT);
+ for (i = 1; i < P4TC_MSGBATCH_SIZE + 1 && tb[i]; i++) {
+ struct nlattr *nest = nla_nest_start(nskb, i);
+
+ ret = tc_ctl_p4_tmpl_gd_1(net, nskb, nlh, tb[i], extack);
+ if (n->nlmsg_flags & NLM_F_ROOT && event == RTM_DELP4TEMPLATE) {
+ if (ret <= 0)
+ goto out;
+ } else {
+ if (ret < 0)
+ goto out;
+ }
+ nla_nest_end(nskb, nest);
+ }
+ nla_nest_end(nskb, root);
+
+ nlmsg_end(nskb, nlh);
+
+ if (event == RTM_GETP4TEMPLATE)
+ return rtnl_unicast(nskb, net, portid);
+
+ return rtnetlink_send(nskb, net, portid, RTNLGRP_TC,
+ n->nlmsg_flags & NLM_F_ECHO);
+out:
+ kfree_skb(nskb);
+ return ret;
+}
+
+static int tc_ctl_p4_tmpl_get(struct sk_buff *skb, struct nlmsghdr *n,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[P4TC_ROOT_MAX + 1];
+ int ret;
+
+ ret = nlmsg_parse(n, sizeof(struct p4tcmsg), tb, P4TC_ROOT_MAX,
+ p4tc_root_policy, extack);
+ if (ret < 0)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(extack, NULL, tb, P4TC_ROOT)) {
+ NL_SET_ERR_MSG(extack,
+ "Netlink P4TC template attributes missing");
+ return -EINVAL;
+ }
+
+ return tc_ctl_p4_tmpl_gd_n(skb, n, tb[P4TC_ROOT],
+ RTM_GETP4TEMPLATE, extack);
+}
+
+static int tc_ctl_p4_tmpl_delete(struct sk_buff *skb, struct nlmsghdr *n,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[P4TC_ROOT_MAX + 1];
+ int ret;
+
+ if (!netlink_capable(skb, CAP_NET_ADMIN))
+ return -EPERM;
+
+ ret = nlmsg_parse(n, sizeof(struct p4tcmsg), tb, P4TC_ROOT_MAX,
+ p4tc_root_policy, extack);
+ if (ret < 0)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(extack, NULL, tb, P4TC_ROOT)) {
+ NL_SET_ERR_MSG(extack,
+ "Netlink P4TC template attributes missing");
+ return -EINVAL;
+ }
+
+ return tc_ctl_p4_tmpl_gd_n(skb, n, tb[P4TC_ROOT],
+ RTM_DELP4TEMPLATE, extack);
+}
+
+static int p4tc_template_put(struct net *net,
+ struct p4tc_template_common *common,
+ struct netlink_ext_ack *extack)
+{
+ /* Every created template is bound to a pipeline */
+ return common->ops->put(common, extack);
+}
+
+static struct p4tc_template_common *
+p4tc_tmpl_cu_1(struct sk_buff *skb, struct net *net, struct nlmsghdr *n,
+ struct nlattr *nla, struct netlink_ext_ack *extack)
+{
+ struct p4tcmsg *t = (struct p4tcmsg *)nlmsg_data(n);
+ struct p4tc_template_common *tmpl;
+ struct p4tc_template_ops *obj_op;
+ struct nlattr *tb[P4TC_MAX + 1];
+ int ret;
+
+ /* All checks will fail at this point because obj_is_valid will return
+ * false. The next patch will make this functional
+ */
+ if (!obj_is_valid(t->obj)) {
+ NL_SET_ERR_MSG(extack, "Invalid object type");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = nla_parse_nested(tb, P4TC_MAX, nla, p4tc_policy, extack);
+ if (ret < 0)
+ goto out;
+
+ if (NL_REQ_ATTR_CHECK(extack, nla, tb, P4TC_PARAMS)) {
+ NL_SET_ERR_MSG(extack, "Must specify object attributes");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ obj_op = (struct p4tc_template_ops *)p4tc_ops[t->obj];
+ tmpl = obj_op->cu(net, n, tb[P4TC_PARAMS], extack);
+ if (IS_ERR(tmpl))
+ return tmpl;
+
+ ret = obj_op->fill_nlmsg(net, skb, tmpl, extack);
+ if (ret < 0)
+ goto put;
+
+ return tmpl;
+
+put:
+ p4tc_template_put(net, tmpl, extack);
+
+out:
+ return ERR_PTR(ret);
+}
+
+static int p4tc_tmpl_cu_n(struct sk_buff *skb, struct nlmsghdr *n,
+ struct nlattr *nla, struct netlink_ext_ack *extack)
+{
+ struct p4tc_template_common *tmpls[P4TC_MSGBATCH_SIZE];
+ struct p4tcmsg *t = (struct p4tcmsg *)nlmsg_data(n);
+ struct nlattr *tb[P4TC_MSGBATCH_SIZE + 1];
+ bool update = p4tc_tmpl_msg_is_update(n);
+ struct net *net = sock_net(skb->sk);
+ u32 portid = NETLINK_CB(skb).portid;
+ struct p4tcmsg *t_new;
+ struct sk_buff *nskb;
+ struct nlmsghdr *nlh;
+ struct nlattr *root;
+ int ret;
+ int i;
+
+ ret = nla_parse_nested(tb, P4TC_MSGBATCH_SIZE, nla, NULL, extack);
+ if (ret < 0)
+ return ret;
+
+ nskb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!nskb)
+ return -ENOMEM;
+
+ nlh = nlmsg_put(nskb, portid, n->nlmsg_seq, n->nlmsg_type,
+ sizeof(*t), n->nlmsg_flags);
+ if (!nlh)
+ goto out;
+
+ t_new = nlmsg_data(nlh);
+ if (!t_new) {
+ NL_SET_ERR_MSG(extack, "Message header is missing");
+ ret = -EINVAL;
+ goto out;
+ }
+ t_new->obj = t->obj;
+
+ root = nla_nest_start(nskb, P4TC_ROOT);
+ if (!root) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < P4TC_MSGBATCH_SIZE && tb[i + 1]; i++) {
+ struct nlattr *nest = nla_nest_start(nskb, i + 1);
+
+ tmpls[i] = p4tc_tmpl_cu_1(nskb, net, nlh, tb[i + 1],
+ extack);
+ if (IS_ERR(tmpls[i])) {
+ ret = PTR_ERR(tmpls[i]);
+ if (i > 0 && update) {
+ nla_nest_cancel(nskb, nest);
+ goto nest_end_root;
+ }
+ goto undo_prev;
+ }
+
+ nla_nest_end(nskb, nest);
+ }
+nest_end_root:
+ nla_nest_end(nskb, root);
+
+ nlmsg_end(nskb, nlh);
+
+ return rtnetlink_send(nskb, net, portid, RTNLGRP_TC,
+ n->nlmsg_flags & NLM_F_ECHO);
+
+undo_prev:
+ if (!update) {
+ while (--i > 0) {
+ struct p4tc_template_common *tmpl = tmpls[i - 1];
+
+ p4tc_template_put(net, tmpl, extack);
+ }
+ }
+
+out:
+ kfree_skb(nskb);
+ return ret;
+}
+
+static int tc_ctl_p4_tmpl_cu(struct sk_buff *skb, struct nlmsghdr *n,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[P4TC_ROOT_MAX + 1];
+ int ret = 0;
+
+ if (!netlink_capable(skb, CAP_NET_ADMIN))
+ return -EPERM;
+
+ ret = nlmsg_parse(n, sizeof(struct p4tcmsg), tb, P4TC_ROOT_MAX,
+ p4tc_root_policy, extack);
+ if (ret < 0)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(extack, NULL, tb, P4TC_ROOT)) {
+ NL_SET_ERR_MSG(extack,
+ "Netlink P4TC template attributes missing");
+ return -EINVAL;
+ }
+
+ return p4tc_tmpl_cu_n(skb, n, tb[P4TC_ROOT], extack);
+}
+
+static int tc_ctl_p4_tmpl_dump_1(struct sk_buff *skb, struct nlattr *arg,
+ struct netlink_callback *cb)
+{
+ struct p4tc_dump_ctx *ctx = (void *)cb->ctx;
+ struct netlink_ext_ack *extack = cb->extack;
+ u32 portid = NETLINK_CB(cb->skb).portid;
+ const struct nlmsghdr *n = cb->nlh;
+ struct p4tc_template_ops *obj_op;
+ struct nlattr *tb[P4TC_MAX + 1];
+ u32 ids[P4TC_PATH_MAX] = {};
+ struct p4tcmsg *t_new;
+ struct nlmsghdr *nlh;
+ struct nlattr *root;
+ struct p4tcmsg *t;
+ int ret;
+
+ ret = nla_parse_nested_deprecated(tb, P4TC_MAX, arg, p4tc_policy,
+ extack);
+ if (ret < 0)
+ return ret;
+
+ t = (struct p4tcmsg *)nlmsg_data(n);
+ /* All checks will fail at this point because obj_is_valid will return
+ * false. The next patch will make this functional
+ */
+ if (!obj_is_valid(t->obj)) {
+ NL_SET_ERR_MSG(extack, "Invalid object type");
+ return -EINVAL;
+ }
+
+ nlh = nlmsg_put(skb, portid, n->nlmsg_seq, n->nlmsg_type,
+ sizeof(*t), n->nlmsg_flags);
+ if (!nlh)
+ return -ENOSPC;
+
+ t_new = nlmsg_data(nlh);
+ t_new->obj = t->obj;
+
+ root = nla_nest_start(skb, P4TC_ROOT);
+
+ obj_op = (struct p4tc_template_ops *)p4tc_ops[t->obj];
+ ret = obj_op->dump(skb, ctx, tb[P4TC_PARAMS], ids, extack);
+ if (ret <= 0)
+ goto out;
+ nla_nest_end(skb, root);
+
+ nlmsg_end(skb, nlh);
+
+ return ret;
+
+out:
+ nlmsg_cancel(skb, nlh);
+ return ret;
+}
+
+static int tc_ctl_p4_tmpl_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct nlattr *tb[P4TC_ROOT_MAX + 1];
+ int ret;
+
+ ret = nlmsg_parse(cb->nlh, sizeof(struct p4tcmsg), tb, P4TC_ROOT_MAX,
+ p4tc_root_policy, cb->extack);
+ if (ret < 0)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(cb->extack, NULL, tb, P4TC_ROOT)) {
+ NL_SET_ERR_MSG(cb->extack,
+ "Netlink P4TC template attributes missing");
+ return -EINVAL;
+ }
+
+ return tc_ctl_p4_tmpl_dump_1(skb, tb[P4TC_ROOT], cb);
+}
+
+static int __init p4tc_template_init(void)
+{
+ u32 obj_id;
+
+ rtnl_register(PF_UNSPEC, RTM_CREATEP4TEMPLATE, tc_ctl_p4_tmpl_cu, NULL,
+ 0);
+ rtnl_register(PF_UNSPEC, RTM_UPDATEP4TEMPLATE, tc_ctl_p4_tmpl_cu, NULL,
+ 0);
+ rtnl_register(PF_UNSPEC, RTM_DELP4TEMPLATE, tc_ctl_p4_tmpl_delete, NULL,
+ 0);
+ rtnl_register(PF_UNSPEC, RTM_GETP4TEMPLATE, tc_ctl_p4_tmpl_get,
+ tc_ctl_p4_tmpl_dump, 0);
+
+ for (obj_id = 0; obj_id < P4TC_OBJ_MAX + 1; obj_id++) {
+ const struct p4tc_template_ops *op = p4tc_ops[obj_id];
+
+ if (!op)
+ continue;
+
+ if (!obj_is_valid(obj_id))
+ continue;
+
+ if (op->init)
+ op->init();
+ }
+
+ return 0;
+}
+
+subsys_initcall(p4tc_template_init);
@@ -94,6 +94,10 @@ static const struct nlmsg_perm nlmsg_route_perms[] = {
{ RTM_NEWTUNNEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
{ RTM_DELTUNNEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
{ RTM_GETTUNNEL, NETLINK_ROUTE_SOCKET__NLMSG_READ },
+ { RTM_CREATEP4TEMPLATE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
+ { RTM_DELP4TEMPLATE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
+ { RTM_GETP4TEMPLATE, NETLINK_ROUTE_SOCKET__NLMSG_READ },
+ { RTM_UPDATEP4TEMPLATE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
};
static const struct nlmsg_perm nlmsg_tcpdiag_perms[] = {
@@ -177,7 +181,7 @@ int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm)
* structures at the top of this file with the new mappings
* before updating the BUILD_BUG_ON() macro!
*/
- BUILD_BUG_ON(RTM_MAX != (RTM_NEWTUNNEL + 3));
+ BUILD_BUG_ON(RTM_MAX != (RTM_CREATEP4TEMPLATE + 3));
err = nlmsg_perm(nlmsg_type, perm, nlmsg_route_perms,
sizeof(nlmsg_route_perms));
break;