forked from mirrors/linux
		
	Most of the original conversion is from the spatch below,
but I edited some and left out other instances that were
either buggy after conversion (where default values don't
fit into the type) or just looked strange.
    @@
    expression attr, def;
    expression val;
    identifier fn =~ "^nla_get_.*";
    fresh identifier dfn = fn ## "_default";
    @@
    (
    -if (attr)
    -  val = fn(attr);
    -else
    -  val = def;
    +val = dfn(attr, def);
    |
    -if (!attr)
    -  val = def;
    -else
    -  val = fn(attr);
    +val = dfn(attr, def);
    |
    -if (!attr)
    -  return def;
    -return fn(attr);
    +return dfn(attr, def);
    |
    -attr ? fn(attr) : def
    +dfn(attr, def)
    |
    -!attr ? def : fn(attr)
    +dfn(attr, def)
    )
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Reviewed-by: Toke Høiland-Jørgensen <toke@kernel.org>
Link: https://patch.msgid.link/20241108114145.0580b8684e7f.I740beeaa2f70ebfc19bfca1045a24d6151992790@changeid
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
		
	
			
		
			
				
	
	
		
			1039 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1039 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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);
 | 
						|
}
 |