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.
---
 include/uapi/linux/in6.h   |  32 +++++
 net/ipv6/exthdrs_options.c | 292 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 324 insertions(+)

diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h
index 38e8e63..a54cf96 100644
--- a/include/uapi/linux/in6.h
+++ b/include/uapi/linux/in6.h
@@ -297,6 +297,38 @@ struct in6_flowlabel_req {
  * MRT6_MAX
  */
 
+/* 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 {
        IPV6_TLV_PERM_NONE,
diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c
index a1b7a2e..da9e257 100644
--- a/net/ipv6/exthdrs_options.c
+++ b/net/ipv6/exthdrs_options.c
@@ -6,11 +6,13 @@
 #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)
 #include <net/xfrm.h>
 #endif
+#include <uapi/linux/genetlink.h>
 #include <uapi/linux/in.h>
 
 /*     Parsing tlv encoded headers.
@@ -560,6 +562,291 @@ static const struct tlv_init_params tlv_init_params[] 
__initconst = {
        }
 };
 
+static struct genl_family tlv_nl_family;
+
+static 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, },
+};
+
+static int tlv_nl_cmd_set(struct sk_buff *skb, struct genl_info *info)
+{
+       struct tlv_tx_param new_tx;
+       int retv = -EINVAL, i;
+       struct tlv_param *tp;
+       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();
+
+       new_tx = *tlv_deref_tx_params(tlv_type);
+
+       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++) {
+                               /* Preferred orders must be unique */
+                               tp = rcu_dereference(tlv_param_table[i]);
+                               if (tp->tx_params.preferred_order == v &&
+                                   i != tlv_type) {
+                                       retv = -EALREADY;
+                                       goto out;
+                               }
+                       }
+                       new_tx.preferred_order = v;
+               }
+       }
+
+       if (!new_tx.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;
+
+                       tp = rcu_dereference(tlv_param_table[i]);
+                       order = tp->tx_params.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_tx.preferred_order = 255 - (pos - 1);
+               else
+                       new_tx.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_tx.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_tx.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_tx.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_tx.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_tx.align_off = v;
+       }
+
+       if (info->attrs[IPV6_TLV_ATTR_MAX_DATA_LEN])
+               new_tx.max_data_len =
+                   nla_get_u8(info->attrs[IPV6_TLV_ATTR_MAX_DATA_LEN]);
+
+       if (info->attrs[IPV6_TLV_ATTR_MIN_DATA_LEN])
+               new_tx.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_tx.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_tx.data_len_off = v;
+       }
+
+       retv = tlv_set_tx_param(tlv_type, &new_tx);
+
+out:
+       rcu_read_unlock();
+       return retv;
+}
+
+static int tlv_nl_cmd_unset(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_tx_param(tlv_type);
+}
+
+static int tlv_fill_info(int tlv_type, struct sk_buff *msg, bool admin)
+{
+       struct tlv_tx_param *tptx;
+       int ret = 0;
+
+       rcu_read_lock();
+
+       tptx = tlv_deref_tx_params(tlv_type);
+
+       if (nla_put_u8(msg, IPV6_TLV_ATTR_TYPE, tlv_type) ||
+           nla_put_u8(msg, IPV6_TLV_ATTR_ORDER, tptx->preferred_order) ||
+           nla_put_u8(msg, IPV6_TLV_ATTR_USER_PERM, tptx->user_perm) ||
+           (admin && nla_put_u8(msg, IPV6_TLV_ATTR_ADMIN_PERM,
+                                tptx->admin_perm)) ||
+           nla_put_u8(msg, IPV6_TLV_ATTR_CLASS, tptx->class) ||
+           nla_put_u8(msg, IPV6_TLV_ATTR_ALIGN_MULT, tptx->align_mult + 1) ||
+           nla_put_u8(msg, IPV6_TLV_ATTR_ALIGN_OFF, tptx->align_off) ||
+           nla_put_u8(msg, IPV6_TLV_ATTR_MIN_DATA_LEN, tptx->min_data_len) ||
+           nla_put_u8(msg, IPV6_TLV_ATTR_MAX_DATA_LEN, tptx->max_data_len) ||
+           nla_put_u8(msg, IPV6_TLV_ATTR_DATA_LEN_MULT,
+                      tptx->data_len_mult + 1) ||
+           nla_put_u8(msg, IPV6_TLV_ATTR_DATA_LEN_OFF, tptx->data_len_off))
+               ret = -1;
+
+       rcu_read_unlock();
+
+       return ret;
+}
+
+static int tlv_dump_info(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_type, skb, admin) < 0) {
+               genlmsg_cancel(skb, hdr);
+               return -EMSGSIZE;
+       }
+
+       genlmsg_end(skb, hdr);
+
+       return 0;
+}
+
+static int tlv_nl_cmd_get(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_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);
+}
+
+static int tlv_nl_dump(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(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;
+}
+
+static const struct genl_ops tlv_nl_ops[] = {
+{
+       .cmd = IPV6_TLV_CMD_SET,
+       .doit = tlv_nl_cmd_set,
+       .policy = tlv_nl_policy,
+       .flags = GENL_ADMIN_PERM,
+},
+{
+       .cmd = IPV6_TLV_CMD_UNSET,
+       .doit = tlv_nl_cmd_unset,
+       .policy = tlv_nl_policy,
+       .flags = GENL_ADMIN_PERM,
+},
+{
+       .cmd = IPV6_TLV_CMD_GET,
+       .doit = tlv_nl_cmd_get,
+       .dumpit = tlv_nl_dump,
+       .policy = tlv_nl_policy,
+},
+};
+
+static struct genl_family tlv_nl_family __ro_after_init = {
+       .hdrsize        = 0,
+       .name           = IPV6_TLV_GENL_NAME,
+       .version        = IPV6_TLV_GENL_VERSION,
+       .maxattr        = IPV6_TLV_ATTR_MAX,
+       .netnsok        = true,
+       .module         = THIS_MODULE,
+       .ops            = tlv_nl_ops,
+       .n_ops          = ARRAY_SIZE(tlv_nl_ops),
+};
+
 static int __init exthdrs_init(void)
 {
        unsigned long check_map[BITS_TO_LONGS(256)];
@@ -596,6 +883,10 @@ static int __init exthdrs_init(void)
                        goto fail;
        }
 
+       ret = genl_register_family(&tlv_nl_family);
+       if (ret < 0)
+               goto fail;
+
        return 0;
 
 fail:
@@ -612,5 +903,6 @@ module_init(exthdrs_init);
 
 static void __exit exthdrs_fini(void)
 {
+       genl_unregister_family(&tlv_nl_family);
 }
 module_exit(exthdrs_fini);
-- 
2.7.4

Reply via email to