[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1554836993-5056-6-git-send-email-tom@quantonium.net>
Date: Tue, 9 Apr 2019 12:09:52 -0700
From: Tom Herbert <tom@...bertland.com>
To: davem@...emloft.net, netdev@...r.kernel.org
Cc: Tom Herbert <tom@...ntonium.net>
Subject: [PATCH v2 net-next 5/6] ip6tlvs: Add netlink interface
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/net/ipv6.h | 9 +-
include/uapi/linux/in6.h | 31 ++++
net/ipv6/exthdrs_core.c | 362 ++++++++++++++++++++++++++++++++++++++++++++-
net/ipv6/exthdrs_options.c | 10 +-
4 files changed, 401 insertions(+), 11 deletions(-)
diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 9b25d08..3d2995f 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>
@@ -450,9 +451,11 @@ int tlv_unset_params(struct tlv_param_table *tlv_param_table,
unsigned char type);
int exthdrs_init(struct tlv_param_table *tlv_param_table,
- const struct tlv_proc_init *init_params,
- int num_init_params);
-void exthdrs_fini(struct tlv_param_table *tlv_param_table);
+ const struct tlv_proc_init *tlv_init_params, int proto,
+ int num_init_params, struct genl_family *tlv_genl_family,
+ char *tlv_genl_name, int tlv_genl_version);
+void exthdrs_fini(struct tlv_param_table *tlv_param_table,
+ struct genl_family *tlv_nl_family);
/* tlv_get_proc assumes rcu_read_lock is held */
static inline struct tlv_proc *tlv_get_proc(
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 98b5bc1..09799bcd 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,14 +659,341 @@ int tlv_unset_params(struct tlv_param_table *tlv_param_table,
}
EXPORT_SYMBOL(tlv_unset_params);
+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 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;
+}
+
+static 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);
+}
+
+static 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;
+}
+
+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;
+}
+
+static 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);
+}
+
+static 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;
+}
+
+extern struct genl_family ipv6_tlv_nl_family;
+
+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 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,
+},
+};
+
int exthdrs_init(struct tlv_param_table *tlv_param_table,
- const struct tlv_proc_init *tlv_init_params,
- int num_init_params)
+ const struct tlv_proc_init *tlv_init_params, int proto,
+ int num_init_params, struct genl_family *tlv_nl_family,
+ char tlv_genl_name[], int tlv_genl_version)
{
unsigned long check_map[BITS_TO_LONGS(256)];
+ const struct genl_ops *tlv_nl_ops;
struct tlv_param_table_data *tpt;
- size_t tsize;
- int i;
+ size_t tsize, ops_size;
+ int i, ret;
+
+ switch (proto) {
+ case ETH_P_IPV6:
+ tlv_nl_ops = ipv6_tlv_nl_ops;
+ ops_size = ARRAY_SIZE(ipv6_tlv_nl_ops);
+ break;
+ default:
+ return -EPROTONOSUPPORT;
+ }
memset(check_map, 0, sizeof(check_map));
@@ -708,12 +1037,35 @@ int exthdrs_init(struct tlv_param_table *tlv_param_table,
RCU_INIT_POINTER(tlv_param_table->data, tpt);
+ memset(tlv_nl_family, 0, sizeof(*tlv_nl_family));
+
+ tlv_nl_family->hdrsize = 0;
+ strncpy(tlv_nl_family->name, tlv_genl_name, GENL_NAMSIZ);
+ tlv_nl_family->version = tlv_genl_version;
+ tlv_nl_family->maxattr = IPV6_TLV_ATTR_MAX;
+ tlv_nl_family->netnsok = true;
+ tlv_nl_family->module = THIS_MODULE;
+ tlv_nl_family->ops = tlv_nl_ops;
+ tlv_nl_family->policy = tlv_nl_policy;
+ tlv_nl_family->n_ops = ops_size;
+
+ ret = genl_register_family(tlv_nl_family);
+ if (ret < 0)
+ goto fail_genl_register;
+
return 0;
+
+fail_genl_register:
+ __tlv_destroy_param_table(tlv_param_table);
+
+ return ret;
}
EXPORT_SYMBOL(exthdrs_init);
-void exthdrs_fini(struct tlv_param_table *tlv_param_table)
+void exthdrs_fini(struct tlv_param_table *tlv_param_table,
+ struct genl_family *tlv_nl_family)
{
__tlv_destroy_param_table(tlv_param_table);
+ genl_unregister_family(tlv_nl_family);
}
EXPORT_SYMBOL(exthdrs_fini);
diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c
index b316143..607a8c2 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)
@@ -251,17 +252,20 @@ static const struct tlv_proc_init tlv_init_params[] __initconst = {
},
};
+struct genl_family ipv6_tlv_nl_family __ro_after_init;
struct tlv_param_table __rcu ipv6_tlv_param_table;
static int __init ipv6_exthdrs_init(void)
{
- return exthdrs_init(&ipv6_tlv_param_table, tlv_init_params,
- ARRAY_SIZE(tlv_init_params));
+ return exthdrs_init(&ipv6_tlv_param_table, tlv_init_params, ETH_P_IPV6,
+ ARRAY_SIZE(tlv_init_params),
+ &ipv6_tlv_nl_family, IPV6_TLV_GENL_NAME,
+ IPV6_TLV_GENL_VERSION);
}
module_init(ipv6_exthdrs_init);
static void __exit ipv6_exthdrs_fini(void)
{
- exthdrs_fini(&ipv6_tlv_param_table);
+ exthdrs_fini(&ipv6_tlv_param_table, &ipv6_tlv_nl_family);
}
module_exit(ipv6_exthdrs_fini);
--
2.7.4
Powered by blists - more mailing lists