| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * IPv6 IOAM implementation |
| * |
| * Author: |
| * Justin Iurman <justin.iurman@uliege.be> |
| */ |
| |
| #include <linux/errno.h> |
| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/net.h> |
| #include <linux/ioam6.h> |
| #include <linux/ioam6_genl.h> |
| #include <linux/rhashtable.h> |
| #include <linux/netdevice.h> |
| |
| #include <net/addrconf.h> |
| #include <net/genetlink.h> |
| #include <net/ioam6.h> |
| #include <net/sch_generic.h> |
| |
| static void ioam6_ns_release(struct ioam6_namespace *ns) |
| { |
| kfree_rcu(ns, rcu); |
| } |
| |
| static void ioam6_sc_release(struct ioam6_schema *sc) |
| { |
| kfree_rcu(sc, rcu); |
| } |
| |
| static void ioam6_free_ns(void *ptr, void *arg) |
| { |
| struct ioam6_namespace *ns = (struct ioam6_namespace *)ptr; |
| |
| if (ns) |
| ioam6_ns_release(ns); |
| } |
| |
| static void ioam6_free_sc(void *ptr, void *arg) |
| { |
| struct ioam6_schema *sc = (struct ioam6_schema *)ptr; |
| |
| if (sc) |
| ioam6_sc_release(sc); |
| } |
| |
| static int ioam6_ns_cmpfn(struct rhashtable_compare_arg *arg, const void *obj) |
| { |
| const struct ioam6_namespace *ns = obj; |
| |
| return (ns->id != *(__be16 *)arg->key); |
| } |
| |
| static int ioam6_sc_cmpfn(struct rhashtable_compare_arg *arg, const void *obj) |
| { |
| const struct ioam6_schema *sc = obj; |
| |
| return (sc->id != *(u32 *)arg->key); |
| } |
| |
| static const struct rhashtable_params rht_ns_params = { |
| .key_len = sizeof(__be16), |
| .key_offset = offsetof(struct ioam6_namespace, id), |
| .head_offset = offsetof(struct ioam6_namespace, head), |
| .automatic_shrinking = true, |
| .obj_cmpfn = ioam6_ns_cmpfn, |
| }; |
| |
| static const struct rhashtable_params rht_sc_params = { |
| .key_len = sizeof(u32), |
| .key_offset = offsetof(struct ioam6_schema, id), |
| .head_offset = offsetof(struct ioam6_schema, head), |
| .automatic_shrinking = true, |
| .obj_cmpfn = ioam6_sc_cmpfn, |
| }; |
| |
| static struct genl_family ioam6_genl_family; |
| |
| static const struct nla_policy ioam6_genl_policy_addns[] = { |
| [IOAM6_ATTR_NS_ID] = { .type = NLA_U16 }, |
| [IOAM6_ATTR_NS_DATA] = { .type = NLA_U32 }, |
| [IOAM6_ATTR_NS_DATA_WIDE] = { .type = NLA_U64 }, |
| }; |
| |
| static const struct nla_policy ioam6_genl_policy_delns[] = { |
| [IOAM6_ATTR_NS_ID] = { .type = NLA_U16 }, |
| }; |
| |
| static const struct nla_policy ioam6_genl_policy_addsc[] = { |
| [IOAM6_ATTR_SC_ID] = { .type = NLA_U32 }, |
| [IOAM6_ATTR_SC_DATA] = { .type = NLA_BINARY, |
| .len = IOAM6_MAX_SCHEMA_DATA_LEN }, |
| }; |
| |
| static const struct nla_policy ioam6_genl_policy_delsc[] = { |
| [IOAM6_ATTR_SC_ID] = { .type = NLA_U32 }, |
| }; |
| |
| static const struct nla_policy ioam6_genl_policy_ns_sc[] = { |
| [IOAM6_ATTR_NS_ID] = { .type = NLA_U16 }, |
| [IOAM6_ATTR_SC_ID] = { .type = NLA_U32 }, |
| [IOAM6_ATTR_SC_NONE] = { .type = NLA_FLAG }, |
| }; |
| |
| static int ioam6_genl_addns(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct ioam6_pernet_data *nsdata; |
| struct ioam6_namespace *ns; |
| u64 data64; |
| u32 data32; |
| __be16 id; |
| int err; |
| |
| if (!info->attrs[IOAM6_ATTR_NS_ID]) |
| return -EINVAL; |
| |
| id = cpu_to_be16(nla_get_u16(info->attrs[IOAM6_ATTR_NS_ID])); |
| nsdata = ioam6_pernet(genl_info_net(info)); |
| |
| mutex_lock(&nsdata->lock); |
| |
| ns = rhashtable_lookup_fast(&nsdata->namespaces, &id, rht_ns_params); |
| if (ns) { |
| err = -EEXIST; |
| goto out_unlock; |
| } |
| |
| ns = kzalloc(sizeof(*ns), GFP_KERNEL); |
| if (!ns) { |
| err = -ENOMEM; |
| goto out_unlock; |
| } |
| |
| ns->id = id; |
| |
| data32 = nla_get_u32_default(info->attrs[IOAM6_ATTR_NS_DATA], |
| IOAM6_U32_UNAVAILABLE); |
| |
| data64 = nla_get_u64_default(info->attrs[IOAM6_ATTR_NS_DATA_WIDE], |
| IOAM6_U64_UNAVAILABLE); |
| |
| ns->data = cpu_to_be32(data32); |
| ns->data_wide = cpu_to_be64(data64); |
| |
| err = rhashtable_lookup_insert_fast(&nsdata->namespaces, &ns->head, |
| rht_ns_params); |
| if (err) |
| kfree(ns); |
| |
| out_unlock: |
| mutex_unlock(&nsdata->lock); |
| return err; |
| } |
| |
| static int ioam6_genl_delns(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct ioam6_pernet_data *nsdata; |
| struct ioam6_namespace *ns; |
| struct ioam6_schema *sc; |
| __be16 id; |
| int err; |
| |
| if (!info->attrs[IOAM6_ATTR_NS_ID]) |
| return -EINVAL; |
| |
| id = cpu_to_be16(nla_get_u16(info->attrs[IOAM6_ATTR_NS_ID])); |
| nsdata = ioam6_pernet(genl_info_net(info)); |
| |
| mutex_lock(&nsdata->lock); |
| |
| ns = rhashtable_lookup_fast(&nsdata->namespaces, &id, rht_ns_params); |
| if (!ns) { |
| err = -ENOENT; |
| goto out_unlock; |
| } |
| |
| sc = rcu_dereference_protected(ns->schema, |
| lockdep_is_held(&nsdata->lock)); |
| |
| err = rhashtable_remove_fast(&nsdata->namespaces, &ns->head, |
| rht_ns_params); |
| if (err) |
| goto out_unlock; |
| |
| if (sc) |
| rcu_assign_pointer(sc->ns, NULL); |
| |
| ioam6_ns_release(ns); |
| |
| out_unlock: |
| mutex_unlock(&nsdata->lock); |
| return err; |
| } |
| |
| static int __ioam6_genl_dumpns_element(struct ioam6_namespace *ns, |
| u32 portid, |
| u32 seq, |
| u32 flags, |
| struct sk_buff *skb, |
| u8 cmd) |
| { |
| struct ioam6_schema *sc; |
| u64 data64; |
| u32 data32; |
| void *hdr; |
| |
| hdr = genlmsg_put(skb, portid, seq, &ioam6_genl_family, flags, cmd); |
| if (!hdr) |
| return -ENOMEM; |
| |
| data32 = be32_to_cpu(ns->data); |
| data64 = be64_to_cpu(ns->data_wide); |
| |
| if (nla_put_u16(skb, IOAM6_ATTR_NS_ID, be16_to_cpu(ns->id)) || |
| (data32 != IOAM6_U32_UNAVAILABLE && |
| nla_put_u32(skb, IOAM6_ATTR_NS_DATA, data32)) || |
| (data64 != IOAM6_U64_UNAVAILABLE && |
| nla_put_u64_64bit(skb, IOAM6_ATTR_NS_DATA_WIDE, |
| data64, IOAM6_ATTR_PAD))) |
| goto nla_put_failure; |
| |
| rcu_read_lock(); |
| |
| sc = rcu_dereference(ns->schema); |
| if (sc && nla_put_u32(skb, IOAM6_ATTR_SC_ID, sc->id)) { |
| rcu_read_unlock(); |
| goto nla_put_failure; |
| } |
| |
| rcu_read_unlock(); |
| |
| genlmsg_end(skb, hdr); |
| return 0; |
| |
| nla_put_failure: |
| genlmsg_cancel(skb, hdr); |
| return -EMSGSIZE; |
| } |
| |
| static int ioam6_genl_dumpns_start(struct netlink_callback *cb) |
| { |
| struct ioam6_pernet_data *nsdata = ioam6_pernet(sock_net(cb->skb->sk)); |
| struct rhashtable_iter *iter = (struct rhashtable_iter *)cb->args[0]; |
| |
| if (!iter) { |
| iter = kmalloc(sizeof(*iter), GFP_KERNEL); |
| if (!iter) |
| return -ENOMEM; |
| |
| cb->args[0] = (long)iter; |
| } |
| |
| rhashtable_walk_enter(&nsdata->namespaces, iter); |
| |
| return 0; |
| } |
| |
| static int ioam6_genl_dumpns_done(struct netlink_callback *cb) |
| { |
| struct rhashtable_iter *iter = (struct rhashtable_iter *)cb->args[0]; |
| |
| rhashtable_walk_exit(iter); |
| kfree(iter); |
| |
| return 0; |
| } |
| |
| static int ioam6_genl_dumpns(struct sk_buff *skb, struct netlink_callback *cb) |
| { |
| struct rhashtable_iter *iter; |
| struct ioam6_namespace *ns; |
| int err; |
| |
| iter = (struct rhashtable_iter *)cb->args[0]; |
| rhashtable_walk_start(iter); |
| |
| for (;;) { |
| ns = rhashtable_walk_next(iter); |
| |
| if (IS_ERR(ns)) { |
| if (PTR_ERR(ns) == -EAGAIN) |
| continue; |
| err = PTR_ERR(ns); |
| goto done; |
| } else if (!ns) { |
| break; |
| } |
| |
| err = __ioam6_genl_dumpns_element(ns, |
| NETLINK_CB(cb->skb).portid, |
| cb->nlh->nlmsg_seq, |
| NLM_F_MULTI, |
| skb, |
| IOAM6_CMD_DUMP_NAMESPACES); |
| if (err) |
| goto done; |
| } |
| |
| err = skb->len; |
| |
| done: |
| rhashtable_walk_stop(iter); |
| return err; |
| } |
| |
| static int ioam6_genl_addsc(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct ioam6_pernet_data *nsdata; |
| int len, len_aligned, err; |
| struct ioam6_schema *sc; |
| u32 id; |
| |
| if (!info->attrs[IOAM6_ATTR_SC_ID] || !info->attrs[IOAM6_ATTR_SC_DATA]) |
| return -EINVAL; |
| |
| id = nla_get_u32(info->attrs[IOAM6_ATTR_SC_ID]); |
| nsdata = ioam6_pernet(genl_info_net(info)); |
| |
| mutex_lock(&nsdata->lock); |
| |
| sc = rhashtable_lookup_fast(&nsdata->schemas, &id, rht_sc_params); |
| if (sc) { |
| err = -EEXIST; |
| goto out_unlock; |
| } |
| |
| len = nla_len(info->attrs[IOAM6_ATTR_SC_DATA]); |
| len_aligned = ALIGN(len, 4); |
| |
| sc = kzalloc(sizeof(*sc) + len_aligned, GFP_KERNEL); |
| if (!sc) { |
| err = -ENOMEM; |
| goto out_unlock; |
| } |
| |
| sc->id = id; |
| sc->len = len_aligned; |
| sc->hdr = cpu_to_be32(sc->id | ((u8)(sc->len / 4) << 24)); |
| nla_memcpy(sc->data, info->attrs[IOAM6_ATTR_SC_DATA], len); |
| |
| err = rhashtable_lookup_insert_fast(&nsdata->schemas, &sc->head, |
| rht_sc_params); |
| if (err) |
| goto free_sc; |
| |
| out_unlock: |
| mutex_unlock(&nsdata->lock); |
| return err; |
| free_sc: |
| kfree(sc); |
| goto out_unlock; |
| } |
| |
| static int ioam6_genl_delsc(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct ioam6_pernet_data *nsdata; |
| struct ioam6_namespace *ns; |
| struct ioam6_schema *sc; |
| int err; |
| u32 id; |
| |
| if (!info->attrs[IOAM6_ATTR_SC_ID]) |
| return -EINVAL; |
| |
| id = nla_get_u32(info->attrs[IOAM6_ATTR_SC_ID]); |
| nsdata = ioam6_pernet(genl_info_net(info)); |
| |
| mutex_lock(&nsdata->lock); |
| |
| sc = rhashtable_lookup_fast(&nsdata->schemas, &id, rht_sc_params); |
| if (!sc) { |
| err = -ENOENT; |
| goto out_unlock; |
| } |
| |
| ns = rcu_dereference_protected(sc->ns, lockdep_is_held(&nsdata->lock)); |
| |
| err = rhashtable_remove_fast(&nsdata->schemas, &sc->head, |
| rht_sc_params); |
| if (err) |
| goto out_unlock; |
| |
| if (ns) |
| rcu_assign_pointer(ns->schema, NULL); |
| |
| ioam6_sc_release(sc); |
| |
| out_unlock: |
| mutex_unlock(&nsdata->lock); |
| return err; |
| } |
| |
| static int __ioam6_genl_dumpsc_element(struct ioam6_schema *sc, |
| u32 portid, u32 seq, u32 flags, |
| struct sk_buff *skb, u8 cmd) |
| { |
| struct ioam6_namespace *ns; |
| void *hdr; |
| |
| hdr = genlmsg_put(skb, portid, seq, &ioam6_genl_family, flags, cmd); |
| if (!hdr) |
| return -ENOMEM; |
| |
| if (nla_put_u32(skb, IOAM6_ATTR_SC_ID, sc->id) || |
| nla_put(skb, IOAM6_ATTR_SC_DATA, sc->len, sc->data)) |
| goto nla_put_failure; |
| |
| rcu_read_lock(); |
| |
| ns = rcu_dereference(sc->ns); |
| if (ns && nla_put_u16(skb, IOAM6_ATTR_NS_ID, be16_to_cpu(ns->id))) { |
| rcu_read_unlock(); |
| goto nla_put_failure; |
| } |
| |
| rcu_read_unlock(); |
| |
| genlmsg_end(skb, hdr); |
| return 0; |
| |
| nla_put_failure: |
| genlmsg_cancel(skb, hdr); |
| return -EMSGSIZE; |
| } |
| |
| static int ioam6_genl_dumpsc_start(struct netlink_callback *cb) |
| { |
| struct ioam6_pernet_data *nsdata = ioam6_pernet(sock_net(cb->skb->sk)); |
| struct rhashtable_iter *iter = (struct rhashtable_iter *)cb->args[0]; |
| |
| if (!iter) { |
| iter = kmalloc(sizeof(*iter), GFP_KERNEL); |
| if (!iter) |
| return -ENOMEM; |
| |
| cb->args[0] = (long)iter; |
| } |
| |
| rhashtable_walk_enter(&nsdata->schemas, iter); |
| |
| return 0; |
| } |
| |
| static int ioam6_genl_dumpsc_done(struct netlink_callback *cb) |
| { |
| struct rhashtable_iter *iter = (struct rhashtable_iter *)cb->args[0]; |
| |
| rhashtable_walk_exit(iter); |
| kfree(iter); |
| |
| return 0; |
| } |
| |
| static int ioam6_genl_dumpsc(struct sk_buff *skb, struct netlink_callback *cb) |
| { |
| struct rhashtable_iter *iter; |
| struct ioam6_schema *sc; |
| int err; |
| |
| iter = (struct rhashtable_iter *)cb->args[0]; |
| rhashtable_walk_start(iter); |
| |
| for (;;) { |
| sc = rhashtable_walk_next(iter); |
| |
| if (IS_ERR(sc)) { |
| if (PTR_ERR(sc) == -EAGAIN) |
| continue; |
| err = PTR_ERR(sc); |
| goto done; |
| } else if (!sc) { |
| break; |
| } |
| |
| err = __ioam6_genl_dumpsc_element(sc, |
| NETLINK_CB(cb->skb).portid, |
| cb->nlh->nlmsg_seq, |
| NLM_F_MULTI, |
| skb, |
| IOAM6_CMD_DUMP_SCHEMAS); |
| if (err) |
| goto done; |
| } |
| |
| err = skb->len; |
| |
| done: |
| rhashtable_walk_stop(iter); |
| return err; |
| } |
| |
| static int ioam6_genl_ns_set_schema(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct ioam6_namespace *ns, *ns_ref; |
| struct ioam6_schema *sc, *sc_ref; |
| struct ioam6_pernet_data *nsdata; |
| __be16 ns_id; |
| u32 sc_id; |
| int err; |
| |
| if (!info->attrs[IOAM6_ATTR_NS_ID] || |
| (!info->attrs[IOAM6_ATTR_SC_ID] && |
| !info->attrs[IOAM6_ATTR_SC_NONE])) |
| return -EINVAL; |
| |
| ns_id = cpu_to_be16(nla_get_u16(info->attrs[IOAM6_ATTR_NS_ID])); |
| nsdata = ioam6_pernet(genl_info_net(info)); |
| |
| mutex_lock(&nsdata->lock); |
| |
| ns = rhashtable_lookup_fast(&nsdata->namespaces, &ns_id, rht_ns_params); |
| if (!ns) { |
| err = -ENOENT; |
| goto out_unlock; |
| } |
| |
| if (info->attrs[IOAM6_ATTR_SC_NONE]) { |
| sc = NULL; |
| } else { |
| sc_id = nla_get_u32(info->attrs[IOAM6_ATTR_SC_ID]); |
| sc = rhashtable_lookup_fast(&nsdata->schemas, &sc_id, |
| rht_sc_params); |
| if (!sc) { |
| err = -ENOENT; |
| goto out_unlock; |
| } |
| } |
| |
| sc_ref = rcu_dereference_protected(ns->schema, |
| lockdep_is_held(&nsdata->lock)); |
| if (sc_ref) |
| rcu_assign_pointer(sc_ref->ns, NULL); |
| rcu_assign_pointer(ns->schema, sc); |
| |
| if (sc) { |
| ns_ref = rcu_dereference_protected(sc->ns, |
| lockdep_is_held(&nsdata->lock)); |
| if (ns_ref) |
| rcu_assign_pointer(ns_ref->schema, NULL); |
| rcu_assign_pointer(sc->ns, ns); |
| } |
| |
| err = 0; |
| |
| out_unlock: |
| mutex_unlock(&nsdata->lock); |
| return err; |
| } |
| |
| static const struct genl_ops ioam6_genl_ops[] = { |
| { |
| .cmd = IOAM6_CMD_ADD_NAMESPACE, |
| .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
| .doit = ioam6_genl_addns, |
| .flags = GENL_ADMIN_PERM, |
| .policy = ioam6_genl_policy_addns, |
| .maxattr = ARRAY_SIZE(ioam6_genl_policy_addns) - 1, |
| }, |
| { |
| .cmd = IOAM6_CMD_DEL_NAMESPACE, |
| .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
| .doit = ioam6_genl_delns, |
| .flags = GENL_ADMIN_PERM, |
| .policy = ioam6_genl_policy_delns, |
| .maxattr = ARRAY_SIZE(ioam6_genl_policy_delns) - 1, |
| }, |
| { |
| .cmd = IOAM6_CMD_DUMP_NAMESPACES, |
| .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
| .start = ioam6_genl_dumpns_start, |
| .dumpit = ioam6_genl_dumpns, |
| .done = ioam6_genl_dumpns_done, |
| .flags = GENL_ADMIN_PERM, |
| }, |
| { |
| .cmd = IOAM6_CMD_ADD_SCHEMA, |
| .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
| .doit = ioam6_genl_addsc, |
| .flags = GENL_ADMIN_PERM, |
| .policy = ioam6_genl_policy_addsc, |
| .maxattr = ARRAY_SIZE(ioam6_genl_policy_addsc) - 1, |
| }, |
| { |
| .cmd = IOAM6_CMD_DEL_SCHEMA, |
| .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
| .doit = ioam6_genl_delsc, |
| .flags = GENL_ADMIN_PERM, |
| .policy = ioam6_genl_policy_delsc, |
| .maxattr = ARRAY_SIZE(ioam6_genl_policy_delsc) - 1, |
| }, |
| { |
| .cmd = IOAM6_CMD_DUMP_SCHEMAS, |
| .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
| .start = ioam6_genl_dumpsc_start, |
| .dumpit = ioam6_genl_dumpsc, |
| .done = ioam6_genl_dumpsc_done, |
| .flags = GENL_ADMIN_PERM, |
| }, |
| { |
| .cmd = IOAM6_CMD_NS_SET_SCHEMA, |
| .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
| .doit = ioam6_genl_ns_set_schema, |
| .flags = GENL_ADMIN_PERM, |
| .policy = ioam6_genl_policy_ns_sc, |
| .maxattr = ARRAY_SIZE(ioam6_genl_policy_ns_sc) - 1, |
| }, |
| }; |
| |
| #define IOAM6_GENL_EV_GRP_OFFSET 0 |
| |
| static const struct genl_multicast_group ioam6_mcgrps[] = { |
| [IOAM6_GENL_EV_GRP_OFFSET] = { .name = IOAM6_GENL_EV_GRP_NAME, |
| .flags = GENL_MCAST_CAP_NET_ADMIN }, |
| }; |
| |
| static int ioam6_event_put_trace(struct sk_buff *skb, |
| struct ioam6_trace_hdr *trace, |
| unsigned int len) |
| { |
| if (nla_put_u16(skb, IOAM6_EVENT_ATTR_TRACE_NAMESPACE, |
| be16_to_cpu(trace->namespace_id)) || |
| nla_put_u8(skb, IOAM6_EVENT_ATTR_TRACE_NODELEN, trace->nodelen) || |
| nla_put_u32(skb, IOAM6_EVENT_ATTR_TRACE_TYPE, |
| be32_to_cpu(trace->type_be32)) || |
| nla_put(skb, IOAM6_EVENT_ATTR_TRACE_DATA, |
| len - sizeof(struct ioam6_trace_hdr) - trace->remlen * 4, |
| trace->data + trace->remlen * 4)) |
| return 1; |
| |
| return 0; |
| } |
| |
| void ioam6_event(enum ioam6_event_type type, struct net *net, gfp_t gfp, |
| void *opt, unsigned int opt_len) |
| { |
| struct nlmsghdr *nlh; |
| struct sk_buff *skb; |
| |
| if (!genl_has_listeners(&ioam6_genl_family, net, |
| IOAM6_GENL_EV_GRP_OFFSET)) |
| return; |
| |
| skb = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp); |
| if (!skb) |
| return; |
| |
| nlh = genlmsg_put(skb, 0, 0, &ioam6_genl_family, 0, type); |
| if (!nlh) |
| goto nla_put_failure; |
| |
| switch (type) { |
| case IOAM6_EVENT_UNSPEC: |
| WARN_ON_ONCE(1); |
| break; |
| case IOAM6_EVENT_TRACE: |
| if (ioam6_event_put_trace(skb, (struct ioam6_trace_hdr *)opt, |
| opt_len)) |
| goto nla_put_failure; |
| break; |
| } |
| |
| genlmsg_end(skb, nlh); |
| genlmsg_multicast_netns(&ioam6_genl_family, net, skb, 0, |
| IOAM6_GENL_EV_GRP_OFFSET, gfp); |
| return; |
| |
| nla_put_failure: |
| nlmsg_free(skb); |
| } |
| |
| static struct genl_family ioam6_genl_family __ro_after_init = { |
| .name = IOAM6_GENL_NAME, |
| .version = IOAM6_GENL_VERSION, |
| .netnsok = true, |
| .parallel_ops = true, |
| .ops = ioam6_genl_ops, |
| .n_ops = ARRAY_SIZE(ioam6_genl_ops), |
| .resv_start_op = IOAM6_CMD_NS_SET_SCHEMA + 1, |
| .mcgrps = ioam6_mcgrps, |
| .n_mcgrps = ARRAY_SIZE(ioam6_mcgrps), |
| .module = THIS_MODULE, |
| }; |
| |
| struct ioam6_namespace *ioam6_namespace(struct net *net, __be16 id) |
| { |
| struct ioam6_pernet_data *nsdata = ioam6_pernet(net); |
| |
| return rhashtable_lookup_fast(&nsdata->namespaces, &id, rht_ns_params); |
| } |
| |
| static void __ioam6_fill_trace_data(struct sk_buff *skb, |
| struct ioam6_namespace *ns, |
| struct ioam6_trace_hdr *trace, |
| struct ioam6_schema *sc, |
| u8 sclen, bool is_input) |
| { |
| struct timespec64 ts; |
| ktime_t tstamp; |
| u64 raw64; |
| u32 raw32; |
| u16 raw16; |
| u8 *data; |
| u8 byte; |
| |
| data = trace->data + trace->remlen * 4 - trace->nodelen * 4 - sclen * 4; |
| |
| /* hop_lim and node_id */ |
| if (trace->type.bit0) { |
| byte = ipv6_hdr(skb)->hop_limit; |
| if (is_input) |
| byte--; |
| |
| raw32 = dev_net(skb_dst(skb)->dev)->ipv6.sysctl.ioam6_id; |
| |
| *(__be32 *)data = cpu_to_be32((byte << 24) | raw32); |
| data += sizeof(__be32); |
| } |
| |
| /* ingress_if_id and egress_if_id */ |
| if (trace->type.bit1) { |
| if (!skb->dev) |
| raw16 = IOAM6_U16_UNAVAILABLE; |
| else |
| raw16 = (__force u16)READ_ONCE(__in6_dev_get(skb->dev)->cnf.ioam6_id); |
| |
| *(__be16 *)data = cpu_to_be16(raw16); |
| data += sizeof(__be16); |
| |
| if (skb_dst(skb)->dev->flags & IFF_LOOPBACK) |
| raw16 = IOAM6_U16_UNAVAILABLE; |
| else |
| raw16 = (__force u16)READ_ONCE(__in6_dev_get(skb_dst(skb)->dev)->cnf.ioam6_id); |
| |
| *(__be16 *)data = cpu_to_be16(raw16); |
| data += sizeof(__be16); |
| } |
| |
| /* timestamp seconds */ |
| if (trace->type.bit2) { |
| if (!skb->dev) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| } else { |
| tstamp = skb_tstamp_cond(skb, true); |
| ts = ktime_to_timespec64(tstamp); |
| |
| *(__be32 *)data = cpu_to_be32((u32)ts.tv_sec); |
| } |
| data += sizeof(__be32); |
| } |
| |
| /* timestamp subseconds */ |
| if (trace->type.bit3) { |
| if (!skb->dev) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| } else { |
| if (!trace->type.bit2) { |
| tstamp = skb_tstamp_cond(skb, true); |
| ts = ktime_to_timespec64(tstamp); |
| } |
| |
| *(__be32 *)data = cpu_to_be32((u32)(ts.tv_nsec / NSEC_PER_USEC)); |
| } |
| data += sizeof(__be32); |
| } |
| |
| /* transit delay */ |
| if (trace->type.bit4) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* namespace data */ |
| if (trace->type.bit5) { |
| *(__be32 *)data = ns->data; |
| data += sizeof(__be32); |
| } |
| |
| /* queue depth */ |
| if (trace->type.bit6) { |
| struct netdev_queue *queue; |
| struct Qdisc *qdisc; |
| __u32 qlen, backlog; |
| |
| if (skb_dst(skb)->dev->flags & IFF_LOOPBACK) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| } else { |
| queue = skb_get_tx_queue(skb_dst(skb)->dev, skb); |
| qdisc = rcu_dereference(queue->qdisc); |
| qdisc_qstats_qlen_backlog(qdisc, &qlen, &backlog); |
| |
| *(__be32 *)data = cpu_to_be32(backlog); |
| } |
| data += sizeof(__be32); |
| } |
| |
| /* checksum complement */ |
| if (trace->type.bit7) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* hop_lim and node_id (wide) */ |
| if (trace->type.bit8) { |
| byte = ipv6_hdr(skb)->hop_limit; |
| if (is_input) |
| byte--; |
| |
| raw64 = dev_net(skb_dst(skb)->dev)->ipv6.sysctl.ioam6_id_wide; |
| |
| *(__be64 *)data = cpu_to_be64(((u64)byte << 56) | raw64); |
| data += sizeof(__be64); |
| } |
| |
| /* ingress_if_id and egress_if_id (wide) */ |
| if (trace->type.bit9) { |
| if (!skb->dev) |
| raw32 = IOAM6_U32_UNAVAILABLE; |
| else |
| raw32 = READ_ONCE(__in6_dev_get(skb->dev)->cnf.ioam6_id_wide); |
| |
| *(__be32 *)data = cpu_to_be32(raw32); |
| data += sizeof(__be32); |
| |
| if (skb_dst(skb)->dev->flags & IFF_LOOPBACK) |
| raw32 = IOAM6_U32_UNAVAILABLE; |
| else |
| raw32 = READ_ONCE(__in6_dev_get(skb_dst(skb)->dev)->cnf.ioam6_id_wide); |
| |
| *(__be32 *)data = cpu_to_be32(raw32); |
| data += sizeof(__be32); |
| } |
| |
| /* namespace data (wide) */ |
| if (trace->type.bit10) { |
| *(__be64 *)data = ns->data_wide; |
| data += sizeof(__be64); |
| } |
| |
| /* buffer occupancy */ |
| if (trace->type.bit11) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* bit12 undefined: filled with empty value */ |
| if (trace->type.bit12) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* bit13 undefined: filled with empty value */ |
| if (trace->type.bit13) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* bit14 undefined: filled with empty value */ |
| if (trace->type.bit14) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* bit15 undefined: filled with empty value */ |
| if (trace->type.bit15) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* bit16 undefined: filled with empty value */ |
| if (trace->type.bit16) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* bit17 undefined: filled with empty value */ |
| if (trace->type.bit17) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* bit18 undefined: filled with empty value */ |
| if (trace->type.bit18) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* bit19 undefined: filled with empty value */ |
| if (trace->type.bit19) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* bit20 undefined: filled with empty value */ |
| if (trace->type.bit20) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* bit21 undefined: filled with empty value */ |
| if (trace->type.bit21) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); |
| data += sizeof(__be32); |
| } |
| |
| /* opaque state snapshot */ |
| if (trace->type.bit22) { |
| if (!sc) { |
| *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE >> 8); |
| } else { |
| *(__be32 *)data = sc->hdr; |
| data += sizeof(__be32); |
| |
| memcpy(data, sc->data, sc->len); |
| } |
| } |
| } |
| |
| /* called with rcu_read_lock() */ |
| void ioam6_fill_trace_data(struct sk_buff *skb, |
| struct ioam6_namespace *ns, |
| struct ioam6_trace_hdr *trace, |
| bool is_input) |
| { |
| struct ioam6_schema *sc; |
| u8 sclen = 0; |
| |
| /* Skip if Overflow flag is set |
| */ |
| if (trace->overflow) |
| return; |
| |
| /* NodeLen does not include Opaque State Snapshot length. We need to |
| * take it into account if the corresponding bit is set (bit 22) and |
| * if the current IOAM namespace has an active schema attached to it |
| */ |
| sc = rcu_dereference(ns->schema); |
| if (trace->type.bit22) { |
| sclen = sizeof_field(struct ioam6_schema, hdr) / 4; |
| |
| if (sc) |
| sclen += sc->len / 4; |
| } |
| |
| /* If there is no space remaining, we set the Overflow flag and we |
| * skip without filling the trace |
| */ |
| if (!trace->remlen || trace->remlen < trace->nodelen + sclen) { |
| trace->overflow = 1; |
| return; |
| } |
| |
| __ioam6_fill_trace_data(skb, ns, trace, sc, sclen, is_input); |
| trace->remlen -= trace->nodelen + sclen; |
| } |
| |
| static int __net_init ioam6_net_init(struct net *net) |
| { |
| struct ioam6_pernet_data *nsdata; |
| int err = -ENOMEM; |
| |
| nsdata = kzalloc(sizeof(*nsdata), GFP_KERNEL); |
| if (!nsdata) |
| goto out; |
| |
| mutex_init(&nsdata->lock); |
| net->ipv6.ioam6_data = nsdata; |
| |
| err = rhashtable_init(&nsdata->namespaces, &rht_ns_params); |
| if (err) |
| goto free_nsdata; |
| |
| err = rhashtable_init(&nsdata->schemas, &rht_sc_params); |
| if (err) |
| goto free_rht_ns; |
| |
| out: |
| return err; |
| free_rht_ns: |
| rhashtable_destroy(&nsdata->namespaces); |
| free_nsdata: |
| kfree(nsdata); |
| net->ipv6.ioam6_data = NULL; |
| goto out; |
| } |
| |
| static void __net_exit ioam6_net_exit(struct net *net) |
| { |
| struct ioam6_pernet_data *nsdata = ioam6_pernet(net); |
| |
| rhashtable_free_and_destroy(&nsdata->namespaces, ioam6_free_ns, NULL); |
| rhashtable_free_and_destroy(&nsdata->schemas, ioam6_free_sc, NULL); |
| |
| kfree(nsdata); |
| } |
| |
| static struct pernet_operations ioam6_net_ops = { |
| .init = ioam6_net_init, |
| .exit = ioam6_net_exit, |
| }; |
| |
| int __init ioam6_init(void) |
| { |
| int err = register_pernet_subsys(&ioam6_net_ops); |
| if (err) |
| goto out; |
| |
| err = genl_register_family(&ioam6_genl_family); |
| if (err) |
| goto out_unregister_pernet_subsys; |
| |
| #ifdef CONFIG_IPV6_IOAM6_LWTUNNEL |
| err = ioam6_iptunnel_init(); |
| if (err) |
| goto out_unregister_genl; |
| #endif |
| |
| pr_info("In-situ OAM (IOAM) with IPv6\n"); |
| |
| out: |
| return err; |
| #ifdef CONFIG_IPV6_IOAM6_LWTUNNEL |
| out_unregister_genl: |
| genl_unregister_family(&ioam6_genl_family); |
| #endif |
| out_unregister_pernet_subsys: |
| unregister_pernet_subsys(&ioam6_net_ops); |
| goto out; |
| } |
| |
| void ioam6_exit(void) |
| { |
| #ifdef CONFIG_IPV6_IOAM6_LWTUNNEL |
| ioam6_iptunnel_exit(); |
| #endif |
| genl_unregister_family(&ioam6_genl_family); |
| unregister_pernet_subsys(&ioam6_net_ops); |
| } |