]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
net/sched: add tunnel option support to act_tunnel_key
authorSimon Horman <simon.horman@netronome.com>
Wed, 27 Jun 2018 04:39:37 +0000 (21:39 -0700)
committerDavid S. Miller <davem@davemloft.net>
Fri, 29 Jun 2018 14:50:26 +0000 (23:50 +0900)
Allow setting tunnel options using the act_tunnel_key action.

Options are expressed as class:type:data and multiple options
may be listed using a comma delimiter.

 # ip link add name geneve0 type geneve dstport 0 external
 # tc qdisc add dev eth0 ingress
 # tc filter add dev eth0 protocol ip parent ffff: \
     flower indev eth0 \
        ip_proto udp \
        action tunnel_key \
            set src_ip 10.0.99.192 \
            dst_ip 10.0.99.193 \
            dst_port 6081 \
            id 11 \
            geneve_opts 0102:80:00800022,0102:80:00800022 \
    action mirred egress redirect dev geneve0

Signed-off-by: Simon Horman <simon.horman@netronome.com>
Signed-off-by: Pieter Jansen van Vuuren <pieter.jansenvanvuuren@netronome.com>
Reviewed-by: Jakub Kicinski <jakub.kicinski@netronome.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/uapi/linux/tc_act/tc_tunnel_key.h
net/sched/act_tunnel_key.c

index 72bbefe5d1d12234fcf34f00fd32acb0a3555fcb..e284fec8c467cc7bfdb121432f63dadfce42b712 100644 (file)
@@ -36,9 +36,35 @@ enum {
        TCA_TUNNEL_KEY_PAD,
        TCA_TUNNEL_KEY_ENC_DST_PORT,    /* be16 */
        TCA_TUNNEL_KEY_NO_CSUM,         /* u8 */
+       TCA_TUNNEL_KEY_ENC_OPTS,        /* Nested TCA_TUNNEL_KEY_ENC_OPTS_
+                                        * attributes
+                                        */
        __TCA_TUNNEL_KEY_MAX,
 };
 
 #define TCA_TUNNEL_KEY_MAX (__TCA_TUNNEL_KEY_MAX - 1)
 
+enum {
+       TCA_TUNNEL_KEY_ENC_OPTS_UNSPEC,
+       TCA_TUNNEL_KEY_ENC_OPTS_GENEVE,         /* Nested
+                                                * TCA_TUNNEL_KEY_ENC_OPTS_
+                                                * attributes
+                                                */
+       __TCA_TUNNEL_KEY_ENC_OPTS_MAX,
+};
+
+#define TCA_TUNNEL_KEY_ENC_OPTS_MAX (__TCA_TUNNEL_KEY_ENC_OPTS_MAX - 1)
+
+enum {
+       TCA_TUNNEL_KEY_ENC_OPT_GENEVE_UNSPEC,
+       TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS,            /* be16 */
+       TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE,             /* u8 */
+       TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA,             /* 4 to 128 bytes */
+
+       __TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX,
+};
+
+#define TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX \
+       (__TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX - 1)
+
 #endif
index 20e98ed8d498b3e384b0fa275dfe6d622b534d0f..ea203e386a9252bca1a4050b6160438b06c5bc47 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/kernel.h>
 #include <linux/skbuff.h>
 #include <linux/rtnetlink.h>
+#include <net/geneve.h>
 #include <net/netlink.h>
 #include <net/pkt_sched.h>
 #include <net/dst.h>
@@ -57,6 +58,135 @@ static int tunnel_key_act(struct sk_buff *skb, const struct tc_action *a,
        return action;
 }
 
+static const struct nla_policy
+enc_opts_policy[TCA_TUNNEL_KEY_ENC_OPTS_MAX + 1] = {
+       [TCA_TUNNEL_KEY_ENC_OPTS_GENEVE]        = { .type = NLA_NESTED },
+};
+
+static const struct nla_policy
+geneve_opt_policy[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX + 1] = {
+       [TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS]      = { .type = NLA_U16 },
+       [TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE]       = { .type = NLA_U8 },
+       [TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA]       = { .type = NLA_BINARY,
+                                                      .len = 128 },
+};
+
+static int
+tunnel_key_copy_geneve_opt(const struct nlattr *nla, void *dst, int dst_len,
+                          struct netlink_ext_ack *extack)
+{
+       struct nlattr *tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX + 1];
+       int err, data_len, opt_len;
+       u8 *data;
+
+       err = nla_parse_nested(tb, TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX,
+                              nla, geneve_opt_policy, extack);
+       if (err < 0)
+               return err;
+
+       if (!tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS] ||
+           !tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE] ||
+           !tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA]) {
+               NL_SET_ERR_MSG(extack, "Missing tunnel key geneve option class, type or data");
+               return -EINVAL;
+       }
+
+       data = nla_data(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA]);
+       data_len = nla_len(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA]);
+       if (data_len < 4) {
+               NL_SET_ERR_MSG(extack, "Tunnel key geneve option data is less than 4 bytes long");
+               return -ERANGE;
+       }
+       if (data_len % 4) {
+               NL_SET_ERR_MSG(extack, "Tunnel key geneve option data is not a multiple of 4 bytes long");
+               return -ERANGE;
+       }
+
+       opt_len = sizeof(struct geneve_opt) + data_len;
+       if (dst) {
+               struct geneve_opt *opt = dst;
+
+               WARN_ON(dst_len < opt_len);
+
+               opt->opt_class =
+                       nla_get_be16(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS]);
+               opt->type = nla_get_u8(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE]);
+               opt->length = data_len / 4; /* length is in units of 4 bytes */
+               opt->r1 = 0;
+               opt->r2 = 0;
+               opt->r3 = 0;
+
+               memcpy(opt + 1, data, data_len);
+       }
+
+       return opt_len;
+}
+
+static int tunnel_key_copy_opts(const struct nlattr *nla, u8 *dst,
+                               int dst_len, struct netlink_ext_ack *extack)
+{
+       int err, rem, opt_len, len = nla_len(nla), opts_len = 0;
+       const struct nlattr *attr, *head = nla_data(nla);
+
+       err = nla_validate(head, len, TCA_TUNNEL_KEY_ENC_OPTS_MAX,
+                          enc_opts_policy, extack);
+       if (err)
+               return err;
+
+       nla_for_each_attr(attr, head, len, rem) {
+               switch (nla_type(attr)) {
+               case TCA_TUNNEL_KEY_ENC_OPTS_GENEVE:
+                       opt_len = tunnel_key_copy_geneve_opt(attr, dst,
+                                                            dst_len, extack);
+                       if (opt_len < 0)
+                               return opt_len;
+                       opts_len += opt_len;
+                       if (dst) {
+                               dst_len -= opt_len;
+                               dst += opt_len;
+                       }
+                       break;
+               }
+       }
+
+       if (!opts_len) {
+               NL_SET_ERR_MSG(extack, "Empty list of tunnel options");
+               return -EINVAL;
+       }
+
+       if (rem > 0) {
+               NL_SET_ERR_MSG(extack, "Trailing data after parsing tunnel key options attributes");
+               return -EINVAL;
+       }
+
+       return opts_len;
+}
+
+static int tunnel_key_get_opts_len(struct nlattr *nla,
+                                  struct netlink_ext_ack *extack)
+{
+       return tunnel_key_copy_opts(nla, NULL, 0, extack);
+}
+
+static int tunnel_key_opts_set(struct nlattr *nla, struct ip_tunnel_info *info,
+                              int opts_len, struct netlink_ext_ack *extack)
+{
+       info->options_len = opts_len;
+       switch (nla_type(nla_data(nla))) {
+       case TCA_TUNNEL_KEY_ENC_OPTS_GENEVE:
+#if IS_ENABLED(CONFIG_INET)
+               info->key.tun_flags |= TUNNEL_GENEVE_OPT;
+               return tunnel_key_copy_opts(nla, ip_tunnel_info_opts(info),
+                                           opts_len, extack);
+#else
+               return -EAFNOSUPPORT;
+#endif
+       default:
+               NL_SET_ERR_MSG(extack, "Cannot set tunnel options for unknown tunnel type");
+               return -EINVAL;
+       }
+}
+
 static const struct nla_policy tunnel_key_policy[TCA_TUNNEL_KEY_MAX + 1] = {
        [TCA_TUNNEL_KEY_PARMS]      = { .len = sizeof(struct tc_tunnel_key) },
        [TCA_TUNNEL_KEY_ENC_IPV4_SRC] = { .type = NLA_U32 },
@@ -66,6 +196,7 @@ static const struct nla_policy tunnel_key_policy[TCA_TUNNEL_KEY_MAX + 1] = {
        [TCA_TUNNEL_KEY_ENC_KEY_ID]   = { .type = NLA_U32 },
        [TCA_TUNNEL_KEY_ENC_DST_PORT] = {.type = NLA_U16},
        [TCA_TUNNEL_KEY_NO_CSUM]      = { .type = NLA_U8 },
+       [TCA_TUNNEL_KEY_ENC_OPTS]     = { .type = NLA_NESTED },
 };
 
 static int tunnel_key_init(struct net *net, struct nlattr *nla,
@@ -81,6 +212,7 @@ static int tunnel_key_init(struct net *net, struct nlattr *nla,
        struct tcf_tunnel_key *t;
        bool exists = false;
        __be16 dst_port = 0;
+       int opts_len = 0;
        __be64 key_id;
        __be16 flags;
        int ret = 0;
@@ -128,6 +260,15 @@ static int tunnel_key_init(struct net *net, struct nlattr *nla,
                if (tb[TCA_TUNNEL_KEY_ENC_DST_PORT])
                        dst_port = nla_get_be16(tb[TCA_TUNNEL_KEY_ENC_DST_PORT]);
 
+               if (tb[TCA_TUNNEL_KEY_ENC_OPTS]) {
+                       opts_len = tunnel_key_get_opts_len(tb[TCA_TUNNEL_KEY_ENC_OPTS],
+                                                          extack);
+                       if (opts_len < 0) {
+                               ret = opts_len;
+                               goto err_out;
+                       }
+               }
+
                if (tb[TCA_TUNNEL_KEY_ENC_IPV4_SRC] &&
                    tb[TCA_TUNNEL_KEY_ENC_IPV4_DST]) {
                        __be32 saddr;
@@ -138,7 +279,7 @@ static int tunnel_key_init(struct net *net, struct nlattr *nla,
 
                        metadata = __ip_tun_set_dst(saddr, daddr, 0, 0,
                                                    dst_port, flags,
-                                                   key_id, 0);
+                                                   key_id, opts_len);
                } else if (tb[TCA_TUNNEL_KEY_ENC_IPV6_SRC] &&
                           tb[TCA_TUNNEL_KEY_ENC_IPV6_DST]) {
                        struct in6_addr saddr;
@@ -162,6 +303,14 @@ static int tunnel_key_init(struct net *net, struct nlattr *nla,
                        goto err_out;
                }
 
+               if (opts_len) {
+                       ret = tunnel_key_opts_set(tb[TCA_TUNNEL_KEY_ENC_OPTS],
+                                                 &metadata->u.tun_info,
+                                                 opts_len, extack);
+                       if (ret < 0)
+                               goto err_out;
+               }
+
                metadata->u.tun_info.mode |= IP_TUNNEL_INFO_TX;
                break;
        default:
@@ -234,6 +383,61 @@ static void tunnel_key_release(struct tc_action *a)
        }
 }
 
+static int tunnel_key_geneve_opts_dump(struct sk_buff *skb,
+                                      const struct ip_tunnel_info *info)
+{
+       int len = info->options_len;
+       u8 *src = (u8 *)(info + 1);
+       struct nlattr *start;
+
+       start = nla_nest_start(skb, TCA_TUNNEL_KEY_ENC_OPTS_GENEVE);
+       if (!start)
+               return -EMSGSIZE;
+
+       while (len > 0) {
+               struct geneve_opt *opt = (struct geneve_opt *)src;
+
+               if (nla_put_be16(skb, TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS,
+                                opt->opt_class) ||
+                   nla_put_u8(skb, TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE,
+                              opt->type) ||
+                   nla_put(skb, TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA,
+                           opt->length * 4, opt + 1))
+                       return -EMSGSIZE;
+
+               len -= sizeof(struct geneve_opt) + opt->length * 4;
+               src += sizeof(struct geneve_opt) + opt->length * 4;
+       }
+
+       nla_nest_end(skb, start);
+       return 0;
+}
+
+static int tunnel_key_opts_dump(struct sk_buff *skb,
+                               const struct ip_tunnel_info *info)
+{
+       struct nlattr *start;
+       int err;
+
+       if (!info->options_len)
+               return 0;
+
+       start = nla_nest_start(skb, TCA_TUNNEL_KEY_ENC_OPTS);
+       if (!start)
+               return -EMSGSIZE;
+
+       if (info->key.tun_flags & TUNNEL_GENEVE_OPT) {
+               err = tunnel_key_geneve_opts_dump(skb, info);
+               if (err)
+                       return err;
+       } else {
+               return -EINVAL;
+       }
+
+       nla_nest_end(skb, start);
+       return 0;
+}
+
 static int tunnel_key_dump_addresses(struct sk_buff *skb,
                                     const struct ip_tunnel_info *info)
 {
@@ -284,8 +488,9 @@ static int tunnel_key_dump(struct sk_buff *skb, struct tc_action *a,
                goto nla_put_failure;
 
        if (params->tcft_action == TCA_TUNNEL_KEY_ACT_SET) {
-               struct ip_tunnel_key *key =
-                       &params->tcft_enc_metadata->u.tun_info.key;
+               struct ip_tunnel_info *info =
+                       &params->tcft_enc_metadata->u.tun_info;
+               struct ip_tunnel_key *key = &info->key;
                __be32 key_id = tunnel_id_to_key32(key->tun_id);
 
                if (nla_put_be32(skb, TCA_TUNNEL_KEY_ENC_KEY_ID, key_id) ||
@@ -293,7 +498,8 @@ static int tunnel_key_dump(struct sk_buff *skb, struct tc_action *a,
                                              &params->tcft_enc_metadata->u.tun_info) ||
                    nla_put_be16(skb, TCA_TUNNEL_KEY_ENC_DST_PORT, key->tp_dst) ||
                    nla_put_u8(skb, TCA_TUNNEL_KEY_NO_CSUM,
-                              !(key->tun_flags & TUNNEL_CSUM)))
+                              !(key->tun_flags & TUNNEL_CSUM)) ||
+                   tunnel_key_opts_dump(skb, info))
                        goto nla_put_failure;
        }