// 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;
if (!info->attrs[IOAM6_ATTR_NS_DATA])
data32 = IOAM6_U32_UNAVAILABLE;
else
data32 = nla_get_u32(info->attrs[IOAM6_ATTR_NS_DATA]);
if (!info->attrs[IOAM6_ATTR_NS_DATA_WIDE])
data64 = IOAM6_U64_UNAVAILABLE;
else
data64 = nla_get_u64(info->attrs[IOAM6_ATTR_NS_DATA_WIDE]);
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