diff mbox series

[v5,net-next,5/6] ip6tlvs: Add netlink interface

Message ID 1555713416-12209-6-git-send-email-tom@quantonium.net
State Changes Requested
Delegated to: David Miller
Headers show
Series exthdrs: Make ext. headers & options useful - Part I | expand

Commit Message

Tom Herbert April 19, 2019, 10:36 p.m. UTC
Add a netlink interface to manage the TX TLV parameters. Managed
parameters include those for validating and sending TLVs being sent
such as alignment, TLV ordering, length limits, etc.

Signed-off-by: Tom Herbert <tom@quantonium.net>
---
 include/net/ipv6.h         |  18 +++
 include/uapi/linux/in6.h   |  31 +++++
 net/ipv6/exthdrs_core.c    | 280 +++++++++++++++++++++++++++++++++++++++++++++
 net/ipv6/exthdrs_options.c |  81 ++++++++++++-
 4 files changed, 408 insertions(+), 2 deletions(-)

Comments

David Ahern April 22, 2019, 3:43 a.m. UTC | #1
On 4/19/19 4:36 PM, Tom Herbert wrote:
> Add a netlink interface to manage the TX TLV parameters. Managed
> parameters include those for validating and sending TLVs being sent
> such as alignment, TLV ordering, length limits, etc.
> 

Why generic netlink for managing IPv6 extensions?
David Ahern April 22, 2019, 2:55 p.m. UTC | #2
On 4/22/19 6:33 AM, Tom Herbert wrote:
> 
> 
> On Sun, Apr 21, 2019, 11:43 PM David Ahern <dsahern@gmail.com
> <mailto:dsahern@gmail.com>> wrote:
> 
>     On 4/19/19 4:36 PM, Tom Herbert wrote:
>     > Add a netlink interface to manage the TX TLV parameters. Managed
>     > parameters include those for validating and sending TLVs being sent
>     > such as alignment, TLV ordering, length limits, etc.
>     >
> 
>     Why generic netlink for managing IPv6 extensions?
> 
> 
> Hi David,
> 
> It allows control over permissions to use specific options. Also, admin
> can add TLVs in the system without requiring specific kernel support.
> The latter in combination with datagram interfaces made bringing up Path
> MTU option at IETF hackathon a breeze (much easier time than FreeBSD
> guys were having :-) ).
> 

Hi Tom: I was asking why can't this be done with rtnetlink? How does the
genl interface make this easier / better?
Tom Herbert April 23, 2019, 1:15 a.m. UTC | #3
On Mon, Apr 22, 2019 at 7:55 AM David Ahern <dsahern@gmail.com> wrote:
>
> On 4/22/19 6:33 AM, Tom Herbert wrote:
> >
> >
> > On Sun, Apr 21, 2019, 11:43 PM David Ahern <dsahern@gmail.com
> > <mailto:dsahern@gmail.com>> wrote:
> >
> >     On 4/19/19 4:36 PM, Tom Herbert wrote:
> >     > Add a netlink interface to manage the TX TLV parameters. Managed
> >     > parameters include those for validating and sending TLVs being sent
> >     > such as alignment, TLV ordering, length limits, etc.
> >     >
> >
> >     Why generic netlink for managing IPv6 extensions?
> >
> >
> > Hi David,
> >
> > It allows control over permissions to use specific options. Also, admin
> > can add TLVs in the system without requiring specific kernel support.
> > The latter in combination with datagram interfaces made bringing up Path
> > MTU option at IETF hackathon a breeze (much easier time than FreeBSD
> > guys were having :-) ).
> >
>
> Hi Tom: I was asking why can't this be done with rtnetlink? How does the
> genl interface make this easier / better?

David,

Looking at the genl How-to, it seems like genl is appropriate. We can
make family for IPv6 and eventually IPv4. Registeration is straight
forward and it's extensible if we need new parameters (without having
to touch rtnetlink.h).

Is there are particular reason why you think rtnetlink would be more
appropriate?

Thanks,
Tom
David Ahern April 23, 2019, 1:49 a.m. UTC | #4
On 4/22/19 7:15 PM, Tom Herbert wrote:
> Is there are particular reason why you think rtnetlink would be more
> appropriate?

Thinking about this from an app API perspective: rtnetlink is used to
configure IPv6 addresses and IPv6 routes yet extensions will require a
genl interface. Just seems odd for some network manager / FRR / other to
deal with.
Tom Herbert April 23, 2019, 2:15 a.m. UTC | #5
On Mon, Apr 22, 2019 at 6:49 PM David Ahern <dsahern@gmail.com> wrote:
>
> On 4/22/19 7:15 PM, Tom Herbert wrote:
> > Is there are particular reason why you think rtnetlink would be more
> > appropriate?
>
> Thinking about this from an app API perspective: rtnetlink is used to
> configure IPv6 addresses and IPv6 routes yet extensions will require a
> genl interface. Just seems odd for some network manager / FRR / other to
> deal with.

David,

This interface is for configuring general parameters about TLV types,
it's not for setting TLVs on routes or packet. The "ip" command type
interface using genl should be sufficient for that purpose. For
exmple, here is "ip" command I'm using to allow non-privileged users
to set path MTU option (draft-hinden-6man-mtu-option-01):

sudo $QDIR/sbin/ip ip6tlv set type 62 order 80 user-perm 1 class 1
align-mult 2 align-off 0 min-len 4 max-len 4 len-mult 1 len-off 0

Tom
diff mbox series

Patch

diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 9b25d08..a1a0af2 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -17,6 +17,7 @@ 
 #include <linux/hardirq.h>
 #include <linux/jhash.h>
 #include <linux/refcount.h>
+#include <net/genetlink.h>
 #include <net/if_inet6.h>
 #include <net/ndisc.h>
 #include <net/flow.h>
@@ -449,6 +450,23 @@  int tlv_set_params(struct tlv_param_table *tlv_param_table,
 int tlv_unset_params(struct tlv_param_table *tlv_param_table,
 		     unsigned char type);
 
+extern const struct nla_policy tlv_nl_policy[];
+
+int tlv_nl_cmd_set(struct tlv_param_table *tlv_param_table,
+		   struct genl_family *tlv_nl_family,
+		   struct sk_buff *skb, struct genl_info *info);
+int tlv_nl_cmd_unset(struct tlv_param_table *tlv_param_table,
+		     struct genl_family *tlv_nl_family,
+		     struct sk_buff *skb, struct genl_info *info);
+int tlv_nl_cmd_get(struct tlv_param_table *tlv_param_table,
+		   struct genl_family *tlv_nl_family,
+		   struct sk_buff *skb, struct genl_info *info);
+int tlv_fill_info(struct tlv_param_table *tlv_param_table,
+		  int tlv_type, struct sk_buff *msg, bool admin);
+int tlv_nl_dump(struct tlv_param_table *tlv_param_table,
+		struct genl_family *tlv_nl_family,
+		struct sk_buff *skb, struct netlink_callback *cb);
+
 int exthdrs_init(struct tlv_param_table *tlv_param_table,
 		 const struct tlv_proc_init *init_params,
 		 int num_init_params);
diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h
index 6a99ee1..1c79361 100644
--- a/include/uapi/linux/in6.h
+++ b/include/uapi/linux/in6.h
@@ -306,6 +306,37 @@  struct in6_flowlabel_req {
 
 #define IPV6_TLV_CLASS_ANY_DSTOPT      (IPV6_TLV_CLASS_FLAG_RTRDSTOPT | \
 					IPV6_TLV_CLASS_FLAG_DSTOPT)
+/* NETLINK_GENERIC related info for IPv6 TLVs */
+
+#define IPV6_TLV_GENL_NAME	"ipv6-tlv"
+#define IPV6_TLV_GENL_VERSION	0x1
+
+enum {
+	IPV6_TLV_ATTR_UNSPEC,
+	IPV6_TLV_ATTR_TYPE,			/* u8, > 1 */
+	IPV6_TLV_ATTR_ORDER,			/* u8 */
+	IPV6_TLV_ATTR_ADMIN_PERM,		/* u8, perm value */
+	IPV6_TLV_ATTR_USER_PERM,		/* u8, perm value */
+	IPV6_TLV_ATTR_CLASS,			/* u8, 3 bit flags */
+	IPV6_TLV_ATTR_ALIGN_MULT,		/* u8, 1 to 16 */
+	IPV6_TLV_ATTR_ALIGN_OFF,		/* u8, 0 to 15 */
+	IPV6_TLV_ATTR_MIN_DATA_LEN,		/* u8 (option data length) */
+	IPV6_TLV_ATTR_MAX_DATA_LEN,		/* u8 (option data length) */
+	IPV6_TLV_ATTR_DATA_LEN_MULT,		/* u8, 1 to 16 */
+	IPV6_TLV_ATTR_DATA_LEN_OFF,		/* u8, 0 to 15 */
+
+	__IPV6_TLV_ATTR_MAX,
+};
+
+#define IPV6_TLV_ATTR_MAX              (__IPV6_TLV_ATTR_MAX - 1)
+
+enum {
+	IPV6_TLV_CMD_SET,
+	IPV6_TLV_CMD_UNSET,
+	IPV6_TLV_CMD_GET,
+
+	__IPV6_TLV_CMD_MAX,
+};
 
 /* TLV permissions values */
 enum {
diff --git a/net/ipv6/exthdrs_core.c b/net/ipv6/exthdrs_core.c
index 53e92fd..0b56f7a 100644
--- a/net/ipv6/exthdrs_core.c
+++ b/net/ipv6/exthdrs_core.c
@@ -3,7 +3,9 @@ 
  * not configured or static.
  */
 #include <linux/export.h>
+#include <net/genetlink.h>
 #include <net/ipv6.h>
+#include <uapi/linux/genetlink.h>
 
 /*
  * find out if nexthdr is a well-known extension header or a protocol
@@ -657,6 +659,284 @@  int tlv_unset_params(struct tlv_param_table *tlv_param_table,
 }
 EXPORT_SYMBOL(tlv_unset_params);
 
+const struct nla_policy tlv_nl_policy[IPV6_TLV_ATTR_MAX + 1] = {
+	[IPV6_TLV_ATTR_TYPE] =		{ .type = NLA_U8, },
+	[IPV6_TLV_ATTR_ORDER] =		{ .type = NLA_U8, },
+	[IPV6_TLV_ATTR_ADMIN_PERM] =	{ .type = NLA_U8, },
+	[IPV6_TLV_ATTR_USER_PERM] =	{ .type = NLA_U8, },
+	[IPV6_TLV_ATTR_CLASS] =		{ .type = NLA_U8, },
+	[IPV6_TLV_ATTR_ALIGN_MULT] =	{ .type = NLA_U8, },
+	[IPV6_TLV_ATTR_ALIGN_OFF] =	{ .type = NLA_U8, },
+	[IPV6_TLV_ATTR_MIN_DATA_LEN] =	{ .type = NLA_U8, },
+	[IPV6_TLV_ATTR_MAX_DATA_LEN] =	{ .type = NLA_U8, },
+	[IPV6_TLV_ATTR_DATA_LEN_OFF] =	{ .type = NLA_U8, },
+	[IPV6_TLV_ATTR_DATA_LEN_MULT] =	{ .type = NLA_U8, },
+};
+EXPORT_SYMBOL(tlv_nl_policy);
+
+int tlv_nl_cmd_set(struct tlv_param_table *tlv_param_table,
+		   struct genl_family *tlv_nl_family,
+		   struct sk_buff *skb, struct genl_info *info)
+{
+	struct tlv_params new_params;
+	struct tlv_tx_params *tptx;
+	struct tlv_proc *tproc;
+	int retv = -EINVAL, i;
+	u8 tlv_type, v;
+
+	if (!info->attrs[IPV6_TLV_ATTR_TYPE])
+		return -EINVAL;
+
+	tlv_type = nla_get_u8(info->attrs[IPV6_TLV_ATTR_TYPE]);
+	if (tlv_type < 2)
+		return -EINVAL;
+
+	rcu_read_lock();
+
+	/* Base new parameters on existing ones */
+	tproc = tlv_get_proc(tlv_param_table, tlv_type);
+	new_params = tproc->params;
+
+	if (info->attrs[IPV6_TLV_ATTR_ORDER]) {
+		v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_ORDER]);
+		if (v) {
+			for (i = 2; i < 256; i++) {
+				tproc = tlv_get_proc(tlv_param_table, i);
+				tptx = &tproc->params.t;
+
+				/* Preferred orders must be unique */
+				if (tptx->preferred_order == v &&
+				    i != tlv_type) {
+					retv = -EALREADY;
+					goto out;
+				}
+			}
+			new_params.t.preferred_order = v;
+		}
+	}
+
+	if (!new_params.t.preferred_order) {
+		unsigned long check_map[BITS_TO_LONGS(255)];
+		int pos;
+
+		/* Preferred order not specified, automatically set one.
+		 * This is chosen to be the first value after the greatest
+		 * order in use.
+		 */
+		memset(check_map, 0, sizeof(check_map));
+
+		for (i = 2; i < 256; i++) {
+			unsigned int order;
+
+			tproc = tlv_get_proc(tlv_param_table, i);
+			tptx = &tproc->params.t;
+			order = tptx->preferred_order;
+
+			if (!order)
+				continue;
+
+			WARN_ON(test_bit(255 - order, check_map));
+			set_bit(255 - order, check_map);
+		}
+
+		pos = find_first_bit(check_map, 255);
+		if (pos)
+			new_params.t.preferred_order = 255 - (pos - 1);
+		else
+			new_params.t.preferred_order = 255 -
+			    find_first_zero_bit(check_map, sizeof(check_map));
+	}
+
+	if (info->attrs[IPV6_TLV_ATTR_ADMIN_PERM]) {
+		v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_ADMIN_PERM]);
+		if (v > IPV6_TLV_PERM_MAX)
+			goto out;
+		new_params.t.admin_perm = v;
+	}
+
+	if (info->attrs[IPV6_TLV_ATTR_USER_PERM]) {
+		v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_USER_PERM]);
+		if (v > IPV6_TLV_PERM_MAX)
+			goto out;
+		new_params.t.user_perm = v;
+	}
+
+	if (info->attrs[IPV6_TLV_ATTR_CLASS]) {
+		v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_CLASS]);
+		if (v > IPV6_TLV_CLASS_MAX)
+			goto out;
+		new_params.t.class = v;
+	}
+
+	if (info->attrs[IPV6_TLV_ATTR_ALIGN_MULT]) {
+		v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_ALIGN_MULT]);
+		if (v > 16 || v < 1)
+			goto out;
+		new_params.t.align_mult = v - 1;
+	}
+
+	if (info->attrs[IPV6_TLV_ATTR_ALIGN_OFF]) {
+		v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_ALIGN_OFF]);
+		if (v > 15)
+			goto out;
+		new_params.t.align_off = v;
+	}
+
+	if (info->attrs[IPV6_TLV_ATTR_MAX_DATA_LEN])
+		new_params.t.max_data_len =
+		    nla_get_u8(info->attrs[IPV6_TLV_ATTR_MAX_DATA_LEN]);
+
+	if (info->attrs[IPV6_TLV_ATTR_MIN_DATA_LEN])
+		new_params.t.min_data_len =
+		    nla_get_u8(info->attrs[IPV6_TLV_ATTR_MIN_DATA_LEN]);
+
+	if (info->attrs[IPV6_TLV_ATTR_DATA_LEN_MULT]) {
+		v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_DATA_LEN_MULT]);
+		if (v > 16 || v < 1)
+			goto out;
+		new_params.t.data_len_mult = v - 1;
+	}
+
+	if (info->attrs[IPV6_TLV_ATTR_DATA_LEN_OFF]) {
+		v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_DATA_LEN_OFF]);
+		if (v > 15)
+			goto out;
+		new_params.t.data_len_off = v;
+	}
+
+	retv = tlv_set_params(tlv_param_table, tlv_type, &new_params);
+
+out:
+	rcu_read_unlock();
+	return retv;
+}
+EXPORT_SYMBOL(tlv_nl_cmd_set);
+
+int tlv_nl_cmd_unset(struct tlv_param_table *tlv_param_table,
+		     struct genl_family *tlv_nl_family,
+		     struct sk_buff *skb, struct genl_info *info)
+{
+	unsigned int tlv_type;
+
+	if (!info->attrs[IPV6_TLV_ATTR_TYPE])
+		return -EINVAL;
+
+	tlv_type = nla_get_u8(info->attrs[IPV6_TLV_ATTR_TYPE]);
+	if (tlv_type < 2)
+		return -EINVAL;
+
+	return tlv_unset_params(tlv_param_table, tlv_type);
+}
+EXPORT_SYMBOL(tlv_nl_cmd_unset);
+
+int tlv_fill_info(struct tlv_param_table *tlv_param_table,
+		  int tlv_type, struct sk_buff *msg, bool admin)
+{
+	struct tlv_proc *tproc;
+	struct tlv_params *tp;
+	int ret = 0;
+
+	rcu_read_lock();
+
+	tproc = tlv_get_proc(tlv_param_table, tlv_type);
+	tp = &tproc->params;
+
+	if (nla_put_u8(msg, IPV6_TLV_ATTR_TYPE, tlv_type) ||
+	    nla_put_u8(msg, IPV6_TLV_ATTR_ORDER, tp->t.preferred_order) ||
+	    nla_put_u8(msg, IPV6_TLV_ATTR_USER_PERM, tp->t.user_perm) ||
+	    (admin && nla_put_u8(msg, IPV6_TLV_ATTR_ADMIN_PERM,
+				 tp->t.admin_perm)) ||
+	    nla_put_u8(msg, IPV6_TLV_ATTR_CLASS, tp->t.class) ||
+	    nla_put_u8(msg, IPV6_TLV_ATTR_ALIGN_MULT, tp->t.align_mult + 1) ||
+	    nla_put_u8(msg, IPV6_TLV_ATTR_ALIGN_OFF, tp->t.align_off) ||
+	    nla_put_u8(msg, IPV6_TLV_ATTR_MIN_DATA_LEN, tp->t.min_data_len) ||
+	    nla_put_u8(msg, IPV6_TLV_ATTR_MAX_DATA_LEN, tp->t.max_data_len) ||
+	    nla_put_u8(msg, IPV6_TLV_ATTR_DATA_LEN_MULT,
+		       tp->t.data_len_mult + 1) ||
+	    nla_put_u8(msg, IPV6_TLV_ATTR_DATA_LEN_OFF, tp->t.data_len_off))
+		ret = -1;
+
+	rcu_read_unlock();
+
+	return ret;
+}
+EXPORT_SYMBOL(tlv_fill_info);
+
+static int tlv_dump_info(struct tlv_param_table *tlv_param_table,
+			 struct genl_family *tlv_nl_family,
+			 int tlv_type, u32 portid, u32 seq, u32 flags,
+			 struct sk_buff *skb, u8 cmd, bool admin)
+{
+	void *hdr;
+
+	hdr = genlmsg_put(skb, portid, seq, tlv_nl_family, flags, cmd);
+	if (!hdr)
+		return -ENOMEM;
+
+	if (tlv_fill_info(tlv_param_table, tlv_type, skb, admin) < 0) {
+		genlmsg_cancel(skb, hdr);
+		return -EMSGSIZE;
+	}
+
+	genlmsg_end(skb, hdr);
+
+	return 0;
+}
+
+int tlv_nl_cmd_get(struct tlv_param_table *tlv_param_table,
+		   struct genl_family *tlv_nl_family,
+		   struct sk_buff *skb, struct genl_info *info)
+{
+	struct sk_buff *msg;
+	int ret, tlv_type;
+
+	if (!info->attrs[IPV6_TLV_ATTR_TYPE])
+		return -EINVAL;
+
+	tlv_type = nla_get_u8(info->attrs[IPV6_TLV_ATTR_TYPE]);
+	if (tlv_type < 2)
+		return -EINVAL;
+
+	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (!msg)
+		return -ENOMEM;
+
+	ret = tlv_dump_info(tlv_param_table, tlv_nl_family,
+			    tlv_type, info->snd_portid, info->snd_seq, 0, msg,
+			    info->genlhdr->cmd,
+			    netlink_capable(skb, CAP_NET_ADMIN));
+	if (ret < 0) {
+		nlmsg_free(msg);
+		return ret;
+	}
+
+	return genlmsg_reply(msg, info);
+}
+EXPORT_SYMBOL(tlv_nl_cmd_get);
+
+int tlv_nl_dump(struct tlv_param_table *tlv_param_table,
+		struct genl_family *tlv_nl_family,
+		struct sk_buff *skb, struct netlink_callback *cb)
+{
+	int idx = 0, ret, i;
+
+	for (i = 2; i < 256; i++) {
+		if (idx++ < cb->args[0])
+			continue;
+		ret = tlv_dump_info(tlv_param_table, tlv_nl_family, i,
+				    NETLINK_CB(cb->skb).portid,
+				    cb->nlh->nlmsg_seq, NLM_F_MULTI,
+				    skb, IPV6_TLV_CMD_GET,
+				    netlink_capable(cb->skb, CAP_NET_ADMIN));
+		if (ret)
+			break;
+	}
+
+	cb->args[0] = idx;
+	return skb->len;
+}
+EXPORT_SYMBOL(tlv_nl_dump);
+
 int exthdrs_init(struct tlv_param_table *tlv_param_table,
 		 const struct tlv_proc_init *tlv_init_params,
 		 int num_init_params)
diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c
index 042ca14..87f3118 100644
--- a/net/ipv6/exthdrs_options.c
+++ b/net/ipv6/exthdrs_options.c
@@ -6,6 +6,7 @@ 
 #include <linux/socket.h>
 #include <linux/types.h>
 #include <net/calipso.h>
+#include <net/genetlink.h>
 #include <net/ipv6.h>
 #include <net/ip6_route.h>
 #if IS_ENABLED(CONFIG_IPV6_MIP6)
@@ -256,15 +257,91 @@  static const struct tlv_proc_init tlv_init_params[] __initconst = {
 struct tlv_param_table __rcu ipv6_tlv_param_table;
 EXPORT_SYMBOL(ipv6_tlv_param_table);
 
+static int ipv6_tlv_nl_cmd_set(struct sk_buff *skb, struct genl_info *info);
+static int ipv6_tlv_nl_cmd_unset(struct sk_buff *skb, struct genl_info *info);
+static int ipv6_tlv_nl_cmd_get(struct sk_buff *skb, struct genl_info *info);
+static int ipv6_tlv_nl_dump(struct sk_buff *skb, struct netlink_callback *cb);
+
+static const struct genl_ops ipv6_tlv_nl_ops[] = {
+{
+	.cmd = IPV6_TLV_CMD_SET,
+	.doit = ipv6_tlv_nl_cmd_set,
+	.flags = GENL_ADMIN_PERM,
+},
+{
+	.cmd = IPV6_TLV_CMD_UNSET,
+	.doit = ipv6_tlv_nl_cmd_unset,
+	.flags = GENL_ADMIN_PERM,
+},
+{
+	.cmd = IPV6_TLV_CMD_GET,
+	.doit = ipv6_tlv_nl_cmd_get,
+	.dumpit = ipv6_tlv_nl_dump,
+},
+};
+
+struct genl_family ipv6_tlv_nl_family __ro_after_init = {
+	.hdrsize	= 0,
+	.name		= IPV6_TLV_GENL_NAME,
+	.version	= IPV6_TLV_GENL_VERSION,
+	.maxattr	= IPV6_TLV_ATTR_MAX,
+	.policy		= tlv_nl_policy,
+	.netnsok	= true,
+	.parallel_ops	= true,
+	.ops		= ipv6_tlv_nl_ops,
+	.n_ops		= ARRAY_SIZE(ipv6_tlv_nl_ops),
+	.module		= THIS_MODULE,
+};
+
+static int ipv6_tlv_nl_cmd_set(struct sk_buff *skb, struct genl_info *info)
+{
+	return tlv_nl_cmd_set(&ipv6_tlv_param_table, &ipv6_tlv_nl_family,
+			      skb, info);
+}
+
+static int ipv6_tlv_nl_cmd_unset(struct sk_buff *skb, struct genl_info *info)
+{
+	return tlv_nl_cmd_unset(&ipv6_tlv_param_table, &ipv6_tlv_nl_family,
+				skb, info);
+}
+
+static int ipv6_tlv_nl_cmd_get(struct sk_buff *skb, struct genl_info *info)
+{
+	return tlv_nl_cmd_get(&ipv6_tlv_param_table, &ipv6_tlv_nl_family,
+			      skb, info);
+}
+
+static int ipv6_tlv_nl_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	return tlv_nl_dump(&ipv6_tlv_param_table, &ipv6_tlv_nl_family,
+			   skb, cb);
+}
+
 static int __init ipv6_exthdrs_init(void)
 {
-	return exthdrs_init(&ipv6_tlv_param_table, tlv_init_params,
-			    ARRAY_SIZE(tlv_init_params));
+	int err;
+
+	err = genl_register_family(&ipv6_tlv_nl_family);
+	if (err)
+		goto out;
+
+	err = exthdrs_init(&ipv6_tlv_param_table, tlv_init_params,
+			   ARRAY_SIZE(tlv_init_params));
+	if (err)
+		goto out_unregister_genl;
+
+	return 0;
+
+out_unregister_genl:
+	genl_unregister_family(&ipv6_tlv_nl_family);
+out:
+	return err;
 }
 module_init(ipv6_exthdrs_init);
 
 static void __exit ipv6_exthdrs_fini(void)
 {
 	exthdrs_fini(&ipv6_tlv_param_table);
+	genl_unregister_family(&ipv6_tlv_nl_family);
 }
 module_exit(ipv6_exthdrs_fini);