diff mbox series

[net-next,2/5] exthdrs: Registration of TLV handlers and parameters

Message ID 1548221483-3085-3-git-send-email-tom@quantonium.net
State Changes Requested
Delegated to: David Miller
Headers show
Series ipv6: Rework ext. headers infrastructure | expand

Commit Message

Tom Herbert Jan. 23, 2019, 5:31 a.m. UTC
Define a table that contains 256 entries, one for each TLV. Each entry
points to a structure that contains parameters and handler functions
for receiving and transmitting TLVs. The receive and transmit properties
can be managed independently.

TLV transmit properties include a description of limits, alignment,
and preferred ordering. TLV receive properties provide the receiver
handler. A class attribute is defined in both receive and transmit
properties that indicate the type of extension header in which the
TLV may be used (e.g. Hop-by-Hop options, Destination options, or
Destination options before the routing header.

Receive TLV lookup and processing is modified to be a lookup in the
TLV table. tlv_{set,unset}_{rx,tx}_param function can be used to
set attributes in the TLV table. A table containing parameters for
TLVs supported by the kernel and is used to initialize the TLV table.
---
 include/net/ipv6.h         |  58 ++++++++-
 include/uapi/linux/in6.h   |  17 +++
 net/ipv6/exthdrs.c         |  47 ++++---
 net/ipv6/exthdrs_options.c | 314 +++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 389 insertions(+), 47 deletions(-)

Comments

David Miller Jan. 23, 2019, 7:27 p.m. UTC | #1
From: Tom Herbert <tom@herbertland.com>
Date: Tue, 22 Jan 2019 21:31:20 -0800

> Define a table that contains 256 entries, one for each TLV. Each entry
> points to a structure that contains parameters and handler functions
> for receiving and transmitting TLVs. The receive and transmit properties
> can be managed independently.

A new 2K table of pointers, most of which are empty.

No, thank you.
diff mbox series

Patch

diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 8abdcdb..3d3b5a1 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -379,13 +379,61 @@  struct ipv6_txoptions *ipv6_renew_options(struct sock *sk,
 struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space,
 					  struct ipv6_txoptions *opt);
 
-struct tlvtype_proc {
-	int	type;
-	bool	(*func)(struct sk_buff *skb, int offset);
+struct tlv_tx_param {
+	unsigned char preferred_order;
+	unsigned char admin_perm : 2;
+	unsigned char user_perm : 2;
+	unsigned char class : 3;
+	unsigned char align_mult : 4;
+	unsigned char align_off : 4;
+	unsigned char data_len_mult : 4;
+	unsigned char data_len_off : 4;
+	unsigned char min_data_len;
+	unsigned char max_data_len;
 };
 
-extern const struct tlvtype_proc tlvprocdestopt_lst[];
-extern const struct tlvtype_proc tlvprochopopt_lst[];
+struct tlv_rx_param {
+	unsigned char class: 3;
+	bool (*func)(unsigned int class, struct sk_buff *skb, int offset);
+};
+
+struct tlv_param {
+	struct tlv_tx_param tx_params;
+	struct tlv_rx_param rx_params;
+	struct rcu_head rcu;
+};
+
+extern struct tlv_param __rcu *tlv_param_table[256];
+
+/* Preferred TLV ordering (placed by increasing order) */
+#define TLV_PREF_ORDER_HAO		10
+#define TLV_PREF_ORDER_ROUTERALERT	20
+#define TLV_PREF_ORDER_JUMBO		30
+#define TLV_PREF_ORDER_CALIPSO		40
+
+/* tlv_deref_rx_params assume rcu_read_lock is held */
+static inline struct tlv_rx_param *tlv_deref_rx_params(unsigned int type)
+{
+	struct tlv_param *tp = rcu_dereference(tlv_param_table[type]);
+
+	return &tp->rx_params;
+}
+
+/* tlv_deref_tx_params assume rcu_read_lock is held */
+static inline struct tlv_tx_param *tlv_deref_tx_params(unsigned int type)
+{
+	struct tlv_param *tp = rcu_dereference(tlv_param_table[type]);
+
+	return &tp->tx_params;
+}
+
+int tlv_set_param(unsigned char type,
+		  const struct tlv_rx_param *rx_param_tmpl,
+		  const struct tlv_tx_param *tx_param_tmpl);
+int tlv_unset_rx_param(unsigned char type);
+int tlv_set_rx_param(unsigned char type, struct tlv_rx_param *rx_param_tmpl);
+int tlv_unset_tx_param(unsigned char type);
+int tlv_set_tx_param(unsigned char type, struct tlv_tx_param *tx_param_tmpl);
 
 bool ipv6_opt_accepted(const struct sock *sk, const struct sk_buff *skb,
 		       const struct inet6_skb_parm *opt);
diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h
index 71d82fe..38e8e63 100644
--- a/include/uapi/linux/in6.h
+++ b/include/uapi/linux/in6.h
@@ -296,4 +296,21 @@  struct in6_flowlabel_req {
  * ...
  * MRT6_MAX
  */
+
+/* TLV permissions values */
+enum {
+	IPV6_TLV_PERM_NONE,
+	IPV6_TLV_PERM_WITH_CHECK,
+	IPV6_TLV_PERM_NO_CHECK,
+	IPV6_TLV_PERM_MAX = IPV6_TLV_PERM_NO_CHECK
+};
+
+/* Flags for EH type that can use a TLV option */
+#define IPV6_TLV_CLASS_FLAG_HOPOPT	0x1
+#define IPV6_TLV_CLASS_FLAG_RTRDSTOPT	0x2
+#define IPV6_TLV_CLASS_FLAG_DSTOPT	0x4
+#define IPV6_TLV_CLASS_MAX		0x7
+
+#define IPV6_TLV_CLASS_ANY_DSTOPT	(IPV6_TLV_CLASS_FLAG_RTRDSTOPT | \
+					 IPV6_TLV_CLASS_FLAG_DSTOPT)
 #endif /* _UAPI_LINUX_IN6_H */
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 6dbacf1..af4152e 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -100,15 +100,14 @@  static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff,
 
 /* Parse tlv encoded option header (hop-by-hop or destination) */
 
-static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
-			  struct sk_buff *skb,
+static bool ip6_parse_tlv(unsigned int class, struct sk_buff *skb,
 			  int max_count)
 {
 	int len = (skb_transport_header(skb)[1] + 1) << 3;
 	const unsigned char *nh = skb_network_header(skb);
 	int off = skb_network_header_len(skb);
-	const struct tlvtype_proc *curr;
 	bool disallow_unknowns = false;
+	struct tlv_rx_param *tprx;
 	int tlv_count = 0;
 	int padlen = 0;
 
@@ -117,12 +116,16 @@  static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
 		max_count = -max_count;
 	}
 
-	if (skb_transport_offset(skb) + len > skb_headlen(skb))
-		goto bad;
+	if (skb_transport_offset(skb) + len > skb_headlen(skb)) {
+		kfree_skb(skb);
+		return false;
+	}
 
 	off += 2;
 	len -= 2;
 
+	rcu_read_unlock();
+
 	while (len > 0) {
 		int optlen = nh[off + 1] + 2;
 		int i;
@@ -162,19 +165,19 @@  static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
 			if (tlv_count > max_count)
 				goto bad;
 
-			for (curr = procs; curr->type >= 0; curr++) {
-				if (curr->type == nh[off]) {
-					/* type specific length/alignment
-					   checks will be performed in the
-					   func(). */
-					if (curr->func(skb, off) == false)
-						return false;
-					break;
-				}
+			tprx = tlv_deref_rx_params(nh[off]);
+
+			if ((tprx->class & class) && tprx->func) {
+				/* Handler will apply additional checks to
+				 * the TLV
+				 */
+				if (!tprx->func(class, skb, off))
+					goto bad_nofree;
+			} else if (!ip6_tlvopt_unknown(skb, off,
+						       disallow_unknowns)) {
+				/* No appropriate handler, TLV is unknown */
+				goto bad_nofree;
 			}
-			if (curr->type < 0 &&
-			    !ip6_tlvopt_unknown(skb, off, disallow_unknowns))
-				return false;
 
 			padlen = 0;
 			break;
@@ -183,10 +186,14 @@  static bool ip6_parse_tlv(const struct tlvtype_proc *procs,
 		len -= optlen;
 	}
 
-	if (len == 0)
+	if (len == 0) {
+		rcu_read_unlock();
 		return true;
+	}
 bad:
 	kfree_skb(skb);
+bad_nofree:
+	rcu_read_unlock();
 	return false;
 }
 
@@ -220,7 +227,7 @@  static int ipv6_destopt_rcv(struct sk_buff *skb)
 	dstbuf = opt->dst1;
 #endif
 
-	if (ip6_parse_tlv(tlvprocdestopt_lst, skb,
+	if (ip6_parse_tlv(IPV6_TLV_CLASS_FLAG_DSTOPT, skb,
 			  init_net.ipv6.sysctl.max_dst_opts_cnt)) {
 		skb->transport_header += extlen;
 		opt = IP6CB(skb);
@@ -643,7 +650,7 @@  int ipv6_parse_hopopts(struct sk_buff *skb)
 		goto fail_and_free;
 
 	opt->flags |= IP6SKB_HOPBYHOP;
-	if (ip6_parse_tlv(tlvprochopopt_lst, skb,
+	if (ip6_parse_tlv(IPV6_TLV_CLASS_FLAG_HOPOPT, skb,
 			  init_net.ipv6.sysctl.max_hbh_opts_cnt)) {
 		skb->transport_header += extlen;
 		opt = IP6CB(skb);
diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c
index 70266a6..a1b7a2e 100644
--- a/net/ipv6/exthdrs_options.c
+++ b/net/ipv6/exthdrs_options.c
@@ -11,6 +11,7 @@ 
 #if IS_ENABLED(CONFIG_IPV6_MIP6)
 #include <net/xfrm.h>
 #endif
+#include <uapi/linux/in.h>
 
 /*	Parsing tlv encoded headers.
  *
@@ -19,6 +20,8 @@ 
  *	It MUST NOT touch skb->h.
  */
 
+struct tlv_param __rcu *tlv_param_table[256];
+
 struct ipv6_txoptions *
 ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt)
 {
@@ -160,7 +163,7 @@  EXPORT_SYMBOL_GPL(ipv6_fixup_options);
 /* Destination options header */
 
 #if IS_ENABLED(CONFIG_IPV6_MIP6)
-static bool ipv6_dest_hao(struct sk_buff *skb, int optoff)
+static bool ipv6_dest_hao(unsigned int class, struct sk_buff *skb, int optoff)
 {
 	struct ipv6_destopt_hao *hao;
 	struct inet6_skb_parm *opt = IP6CB(skb);
@@ -219,16 +222,6 @@  static bool ipv6_dest_hao(struct sk_buff *skb, int optoff)
 }
 #endif
 
-const struct tlvtype_proc tlvprocdestopt_lst[] = {
-#if IS_ENABLED(CONFIG_IPV6_MIP6)
-	{
-		.type	= IPV6_TLV_HAO,
-		.func	= ipv6_dest_hao,
-	},
-#endif
-	{-1,			NULL}
-};
-
 /* Hop-by-hop options */
 
 /* Note: we cannot rely on skb_dst(skb) before we assign it in
@@ -247,7 +240,7 @@  static inline struct net *ipv6_skb_net(struct sk_buff *skb)
 
 /* Router Alert as of RFC 2711 */
 
-static bool ipv6_hop_ra(struct sk_buff *skb, int optoff)
+static bool ipv6_hop_ra(unsigned int class, struct sk_buff *skb, int optoff)
 {
 	const unsigned char *nh = skb_network_header(skb);
 
@@ -265,7 +258,7 @@  static bool ipv6_hop_ra(struct sk_buff *skb, int optoff)
 
 /* Jumbo payload */
 
-static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff)
+static bool ipv6_hop_jumbo(unsigned int class, struct sk_buff *skb, int optoff)
 {
 	const unsigned char *nh = skb_network_header(skb);
 	struct inet6_dev *idev = __in6_dev_get_safely(skb->dev);
@@ -309,7 +302,8 @@  static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff)
 
 /* CALIPSO RFC 5570 */
 
-static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff)
+static bool ipv6_hop_calipso(unsigned int class, struct sk_buff *skb,
+			     int optoff)
 {
 	const unsigned char *nh = skb_network_header(skb);
 
@@ -329,18 +323,294 @@  static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff)
 	return false;
 }
 
-const struct tlvtype_proc tlvprochopopt_lst[] = {
+/* TLV parameter table functions and structures */
+
+static void tlv_param_release(struct rcu_head *rcu)
+{
+	struct tlv_param *tp = container_of(rcu, struct tlv_param, rcu);
+
+	vfree(tp);
+}
+
+/* Default (unset) values for TX TLV parameters */
+static const struct tlv_param tlv_default_param = {
+	.tx_params.preferred_order = 0,
+	.tx_params.admin_perm = IPV6_TLV_PERM_NO_CHECK,
+	.tx_params.user_perm = IPV6_TLV_PERM_NONE,
+	.tx_params.class = 0,
+	.tx_params.align_mult = (4 - 1), /* Default alignment: 4n + 2 */
+	.tx_params.align_off = 2,
+	.tx_params.min_data_len = 0,
+	.tx_params.max_data_len = 255,
+	.tx_params.data_len_mult = (1 - 1), /* No default length align */
+	.tx_params.data_len_off = 0,
+};
+
+int __tlv_write_param(unsigned char type, const struct tlv_param *tp)
+{
+	static DEFINE_MUTEX(tlv_mutex);
+	struct tlv_param *old;
+
+	mutex_lock(&tlv_mutex);
+
+	old = rcu_dereference_protected(tlv_param_table[type],
+					lockdep_is_held(&tlv_mutex));
+
+	rcu_assign_pointer(tlv_param_table[type], tp);
+
+	if (old != &tlv_default_param) {
+		/* Old table entry is not default. Assume that it was
+		 * vmalloc'ed so schedule a vfree in rcu.
+		 */
+		call_rcu(&old->rcu, tlv_param_release);
+	}
+
+	mutex_unlock(&tlv_mutex);
+
+	return 0;
+}
+
+int tlv_set_param(unsigned char type,
+		  const struct tlv_rx_param *rx_param_tmpl,
+		  const struct tlv_tx_param *tx_param_tmpl)
+{
+	struct tlv_param *tp;
+	int ret;
+
+	if (type < 2)
+		return -EINVAL;
+
+	/* Need to alloc and copy from templates */
+
+	tp = vmalloc(sizeof(*tp));
+	if (!tp)
+		return -ENOMEM;
+
+	memcpy(&tp->rx_params, rx_param_tmpl, sizeof(tp->rx_params));
+	memcpy(&tp->tx_params, tx_param_tmpl, sizeof(tp->tx_params));
+
+	ret = __tlv_write_param(type, tp);
+	if (ret < 0)
+		vfree(tp);
+
+	return ret;
+}
+EXPORT_SYMBOL(tlv_set_param);
+
+int tlv_unset_rx_param(unsigned char type)
+{
+	struct tlv_tx_param *tptx;
+	int ret;
+
+	if (type < 2)
+		return -EINVAL;
+
+	rcu_read_lock();
+
+	tptx = tlv_deref_tx_params(type);
+
+	if (!tptx->preferred_order)
+		ret = __tlv_write_param(type, &tlv_default_param);
+	else
+		ret = tlv_set_param(type, &tlv_default_param.rx_params, tptx);
+
+	rcu_read_unlock();
+
+	return ret;
+}
+EXPORT_SYMBOL(tlv_unset_rx_param);
+
+int tlv_set_rx_param(unsigned char type, struct tlv_rx_param *rx_param_tmpl)
+{
+	struct tlv_tx_param *tptx;
+	int ret;
+
+	if (type < 2)
+		return -EINVAL;
+
+	rcu_read_lock();
+
+	tptx = tlv_deref_tx_params(type);
+
+	ret = tlv_set_param(type, rx_param_tmpl, tptx);
+
+	rcu_read_unlock();
+
+	return ret;
+}
+EXPORT_SYMBOL(tlv_set_rx_param);
+
+int tlv_unset_tx_param(unsigned char type)
+{
+	struct tlv_rx_param *tprx;
+	int ret;
+
+	if (type < 2)
+		return -EINVAL;
+
+	rcu_read_lock();
+
+	tprx = tlv_deref_rx_params(type);
+
+	if (!tprx->class)
+		ret = __tlv_write_param(type, &tlv_default_param);
+	else
+		ret = tlv_set_param(type, tprx, &tlv_default_param.tx_params);
+
+	rcu_read_unlock();
+
+	return ret;
+}
+EXPORT_SYMBOL(tlv_unset_tx_param);
+
+int tlv_set_tx_param(unsigned char type, struct tlv_tx_param *tx_param_tmpl)
+{
+	struct tlv_rx_param *tprx;
+	int ret;
+
+	if (type < 2)
+		return -EINVAL;
+
+	rcu_read_lock();
+
+	tprx = tlv_deref_rx_params(type);
+
+	ret = tlv_set_param(type, tprx, tx_param_tmpl);
+
+	rcu_read_unlock();
+
+	return ret;
+}
+EXPORT_SYMBOL(tlv_set_tx_param);
+
+struct tlv_init_params {
+	int type;
+	struct tlv_tx_param t;
+	struct tlv_rx_param r;
+};
+
+static const struct tlv_init_params tlv_init_params[] __initconst = {
 	{
-		.type	= IPV6_TLV_ROUTERALERT,
-		.func	= ipv6_hop_ra,
+		.type = IPV6_TLV_HAO,
+
+		.t.preferred_order = TLV_PREF_ORDER_HAO,
+		.t.admin_perm = IPV6_TLV_PERM_NO_CHECK,
+		.t.user_perm = IPV6_TLV_PERM_NONE,
+		.t.class = IPV6_TLV_CLASS_FLAG_DSTOPT,
+		.t.align_mult = (8 - 1), /* Align to 8n + 6 */
+		.t.align_off = 6,
+		.t.min_data_len = 16,
+		.t.max_data_len = 16,
+		.t.data_len_mult = (1 - 1), /* Fixed length */
+		.t.data_len_off = 0,
+
+		.r.func = ipv6_dest_hao,
+		.r.class = IPV6_TLV_CLASS_FLAG_DSTOPT,
 	},
 	{
-		.type	= IPV6_TLV_JUMBO,
-		.func	= ipv6_hop_jumbo,
+		.type = IPV6_TLV_ROUTERALERT,
+
+		.t.preferred_order = TLV_PREF_ORDER_ROUTERALERT,
+		.t.admin_perm = IPV6_TLV_PERM_NO_CHECK,
+		.t.user_perm = IPV6_TLV_PERM_NONE,
+		.t.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+		.t.align_mult = (2 - 1), /* Align to 2n */
+		.t.align_off = 0,
+		.t.min_data_len = 2,
+		.t.max_data_len = 2,
+		.t.data_len_mult = (1 - 1), /* Fixed length */
+		.t.data_len_off = 0,
+
+		.r.func = ipv6_hop_ra,
+		.r.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
 	},
 	{
-		.type	= IPV6_TLV_CALIPSO,
-		.func	= ipv6_hop_calipso,
+		.type = IPV6_TLV_JUMBO,
+
+		.t.preferred_order = TLV_PREF_ORDER_JUMBO,
+		.t.admin_perm = IPV6_TLV_PERM_NO_CHECK,
+		.t.user_perm = IPV6_TLV_PERM_NONE,
+		.t.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+		.t.align_mult = (4 - 1), /* Align to 4n + 2 */
+		.t.align_off = 2,
+		.t.min_data_len = 4,
+		.t.max_data_len = 4,
+		.t.data_len_mult = (1 - 1), /* Fixed length */
+		.t.data_len_off = 0,
+
+		.r.func = ipv6_hop_jumbo,
+		.r.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
 	},
-	{ -1, }
+	{
+		.type = IPV6_TLV_CALIPSO,
+
+		.t.preferred_order = TLV_PREF_ORDER_CALIPSO,
+		.t.admin_perm = IPV6_TLV_PERM_NO_CHECK,
+		.t.user_perm = IPV6_TLV_PERM_NONE,
+		.t.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+		.t.align_mult = (4 - 1), /* Align to 4n + 2 */
+		.t.align_off = 2,
+		.t.min_data_len = 8,
+		.t.max_data_len = 252,
+		.t.data_len_mult = (4 - 1), /* Length is multiple of 4 */
+		.t.data_len_off = 0,
+
+		.r.func = ipv6_hop_calipso,
+		.r.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+	}
 };
+
+static int __init exthdrs_init(void)
+{
+	unsigned long check_map[BITS_TO_LONGS(256)];
+	const struct tlv_rx_param *rx_params;
+	const struct tlv_tx_param *tx_params;
+	int i, ret;
+
+	memset(check_map, 0, sizeof(check_map));
+
+	for (i = 2; i < 256; i++)
+		RCU_INIT_POINTER(tlv_param_table[i], &tlv_default_param);
+
+	for (i = 0; i < ARRAY_SIZE(tlv_init_params); i++) {
+		const struct tlv_init_params *tpi = &tlv_init_params[i];
+		unsigned int order = tpi->t.preferred_order;
+
+		WARN_ON(tpi->type < 2); /* Padding TLV initialized? */
+
+		if (order) {
+			WARN_ON(test_bit(order, check_map));
+			set_bit(order, check_map);
+			tx_params = &tpi->t;
+		} else {
+			tx_params = &tlv_default_param.tx_params;
+		}
+
+		if (tpi->r.class)
+			rx_params = &tpi->r;
+		else
+			rx_params = &tlv_default_param.rx_params;
+
+		ret = tlv_set_param(tpi->type, rx_params, tx_params);
+		if (ret < 0)
+			goto fail;
+	}
+
+	return 0;
+
+fail:
+	/* Undo anything that was set. */
+	for (i = 0; i < ARRAY_SIZE(tlv_init_params); i++)
+		__tlv_write_param(tlv_init_params[i].type, &tlv_default_param);
+
+	for (i = 2; i < 256; i++)
+		RCU_INIT_POINTER(tlv_param_table[i], NULL);
+
+	return ret;
+}
+module_init(exthdrs_init);
+
+static void __exit exthdrs_fini(void)
+{
+}
+module_exit(exthdrs_fini);