[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1548221483-3085-3-git-send-email-tom@quantonium.net>
Date: Tue, 22 Jan 2019 21:31:20 -0800
From: Tom Herbert <tom@...bertland.com>
To: davem@...emloft.net, netdev@...r.kernel.org
Cc: Tom Herbert <tom@...ntonium.net>
Subject: [PATCH net-next 2/5] exthdrs: Registration of TLV handlers and parameters
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(-)
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);
--
2.7.4
Powered by blists - more mailing lists