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

Reply via email to