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