This commit allows userspace to configure keyed VTI tunnels by
adding a IFLA_VTI_FLAGS attribute and a VTI_KEYED flag. When set,
the flag causes the tunnel parameter i_flags to be set to
TUNNEL_KEY.

Creating both a non-keyed VTI and a keyed VTI on the same IP
src+dst pair is not useful. Because non-keyed VTIs always accept
packets, in such a configuration the keyed VTI would not receive
any traffic. This is disallowed by modifying the ip_tunnel_find
and vti6_locate functions to treat VTIs on the same src+dst pair
as identical unless they are both keyed (in which case they can
coexist, by design). So attempts to create such duplicate tunnels
- or to change one tunnel in such a way that it would duplicate
another - will fail with EEXIST.

Signed-off-by: Lorenzo Colitti <lore...@google.com>
---
 include/uapi/linux/if_tunnel.h |  4 ++++
 net/ipv4/ip_tunnel.c           | 10 +++++++++-
 net/ipv4/ip_vti.c              | 26 +++++++++++++++++++++++---
 net/ipv6/ip6_vti.c             | 33 +++++++++++++++++++++++++++++++--
 4 files changed, 67 insertions(+), 6 deletions(-)

diff --git a/include/uapi/linux/if_tunnel.h b/include/uapi/linux/if_tunnel.h
index 1b3d148c45..b431b1c209 100644
--- a/include/uapi/linux/if_tunnel.h
+++ b/include/uapi/linux/if_tunnel.h
@@ -148,6 +148,9 @@ enum {
 /* VTI-mode i_flags */
 #define VTI_ISVTI ((__force __be16)0x0001)
 
+/* VTI netlink iflags. */
+#define VTI_KEYED 0x0001
+
 enum {
        IFLA_VTI_UNSPEC,
        IFLA_VTI_LINK,
@@ -156,6 +159,7 @@ enum {
        IFLA_VTI_LOCAL,
        IFLA_VTI_REMOTE,
        IFLA_VTI_FWMARK,
+       IFLA_VTI_FLAGS,
        __IFLA_VTI_MAX,
 };
 
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index f45968bb81..9a0a56b491 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -84,6 +84,14 @@ static bool ip_tunnel_key_match(const struct ip_tunnel_parm 
*p,
                return !(flags & TUNNEL_KEY);
 }
 
+static bool ip_tunnel_match(const struct ip_tunnel_parm *p,
+                           __be32 flags, u8 lookup_flags, __be32 key)
+{
+       return ip_tunnel_key_match(p, flags, lookup_flags, key) ||
+              ((p->i_flags & flags & VTI_ISVTI) &&
+               !(p->i_flags & flags & TUNNEL_KEY));
+}
+
 /* Fallback tunnel: no source, no destination, no key, no options
 
    Tunnel hash table:
@@ -242,7 +250,7 @@ static struct ip_tunnel *ip_tunnel_find(struct 
ip_tunnel_net *itn,
                    remote == t->parms.iph.daddr &&
                    link == t->parms.link &&
                    type == t->dev->type &&
-                   ip_tunnel_key_match(&t->parms, flags, 0, key))
+                   ip_tunnel_match(&t->parms, flags, 0, key))
                        break;
        }
        return t;
diff --git a/net/ipv4/ip_vti.c b/net/ipv4/ip_vti.c
index 9d28433a60..1f52719228 100644
--- a/net/ipv4/ip_vti.c
+++ b/net/ipv4/ip_vti.c
@@ -385,6 +385,16 @@ static int vti4_err(struct sk_buff *skb, u32 info)
        return tunnel ? 0 : -1;
 }
 
+static __be16 vti_flags_to_tnl_flags(__u16 flags)
+{
+       return VTI_ISVTI | ((flags & VTI_KEYED) ? TUNNEL_KEY : 0);
+}
+
+static __u16 tnl_flags_to_vti_flags(__be16 i_flags)
+{
+       return (i_flags & TUNNEL_KEY) ? VTI_KEYED : 0;
+}
+
 static int
 vti_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 {
@@ -525,6 +535,8 @@ static void vti_netlink_parms(struct nlattr *data[],
                              struct ip_tunnel_parm *parms,
                              __u32 *fwmark)
 {
+       __u16 flags = 0;
+
        memset(parms, 0, sizeof(*parms));
 
        parms->iph.protocol = IPPROTO_IPIP;
@@ -532,8 +544,6 @@ static void vti_netlink_parms(struct nlattr *data[],
        if (!data)
                return;
 
-       parms->i_flags = VTI_ISVTI;
-
        if (data[IFLA_VTI_LINK])
                parms->link = nla_get_u32(data[IFLA_VTI_LINK]);
 
@@ -551,6 +561,11 @@ static void vti_netlink_parms(struct nlattr *data[],
 
        if (data[IFLA_VTI_FWMARK])
                *fwmark = nla_get_u32(data[IFLA_VTI_FWMARK]);
+
+       if (data[IFLA_VTI_FLAGS])
+               flags = nla_get_u16(data[IFLA_VTI_FLAGS]);
+
+       parms->i_flags = vti_flags_to_tnl_flags(flags);
 }
 
 static int vti_newlink(struct net *src_net, struct net_device *dev,
@@ -591,6 +606,8 @@ static size_t vti_get_size(const struct net_device *dev)
                nla_total_size(4) +
                /* IFLA_VTI_FWMARK */
                nla_total_size(4) +
+               /* IFLA_VTI_FLAGS */
+               nla_total_size(2) +
                0;
 }
 
@@ -604,7 +621,9 @@ static int vti_fill_info(struct sk_buff *skb, const struct 
net_device *dev)
            nla_put_be32(skb, IFLA_VTI_OKEY, p->o_key) ||
            nla_put_in_addr(skb, IFLA_VTI_LOCAL, p->iph.saddr) ||
            nla_put_in_addr(skb, IFLA_VTI_REMOTE, p->iph.daddr) ||
-           nla_put_u32(skb, IFLA_VTI_FWMARK, t->fwmark))
+           nla_put_u32(skb, IFLA_VTI_FWMARK, t->fwmark) ||
+           nla_put_u16(skb, IFLA_VTI_FLAGS,
+                       tnl_flags_to_vti_flags(p->i_flags)))
                return -EMSGSIZE;
 
        return 0;
@@ -617,6 +636,7 @@ static const struct nla_policy vti_policy[IFLA_VTI_MAX + 1] 
= {
        [IFLA_VTI_LOCAL]        = { .len = FIELD_SIZEOF(struct iphdr, saddr) },
        [IFLA_VTI_REMOTE]       = { .len = FIELD_SIZEOF(struct iphdr, daddr) },
        [IFLA_VTI_FWMARK]       = { .type = NLA_U32 },
+       [IFLA_VTI_FLAGS]        = { .type = NLA_U16 },
 };
 
 static struct rtnl_link_ops vti_link_ops __read_mostly = {
diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c
index bf64821b8a..18c2695dc3 100644
--- a/net/ipv6/ip6_vti.c
+++ b/net/ipv6/ip6_vti.c
@@ -86,6 +86,13 @@ static bool vti6_match_key(const struct ip6_tnl *t, __be32 
key, bool in)
        return !(flags & TUNNEL_KEY) || tunnel_key == key;
 }
 
+static bool vti6_match_tunnel(const struct ip6_tnl *t, struct __ip6_tnl_parm 
*p)
+{
+       return !(t->parms.i_flags & TUNNEL_KEY) ||
+              !(p->i_flags & TUNNEL_KEY) ||
+              vti6_match_key(t, p->i_key, true);
+}
+
 /**
  * vti6_tnl_lookup - fetch tunnel matching the end-point addresses and key
  *   @net: network namespace
@@ -280,7 +287,7 @@ static struct ip6_tnl *vti6_locate(struct net *net, struct 
__ip6_tnl_parm *p,
             tp = &t->next) {
                if (ipv6_addr_equal(local, &t->parms.laddr) &&
                    ipv6_addr_equal(remote, &t->parms.raddr) &&
-                   vti6_match_key(t, p->i_key, true)) {
+                   vti6_match_tunnel(t, p)) {
                        if (create)
                                return NULL;
 
@@ -990,9 +997,21 @@ static int vti6_validate(struct nlattr *tb[], struct 
nlattr *data[],
        return 0;
 }
 
+static __be16 vti_flags_to_tnl_flags(__u16 i_flags)
+{
+       return VTI_ISVTI | ((i_flags & VTI_KEYED) ? TUNNEL_KEY : 0);
+}
+
+static __u16 tnl_flags_to_vti_flags(__be16 i_flags)
+{
+       return (i_flags & TUNNEL_KEY) ? VTI_KEYED : 0;
+}
+
 static void vti6_netlink_parms(struct nlattr *data[],
                               struct __ip6_tnl_parm *parms)
 {
+       __u16 flags = 0;
+
        memset(parms, 0, sizeof(*parms));
 
        if (!data)
@@ -1015,6 +1034,11 @@ static void vti6_netlink_parms(struct nlattr *data[],
 
        if (data[IFLA_VTI_FWMARK])
                parms->fwmark = nla_get_u32(data[IFLA_VTI_FWMARK]);
+
+       if (data[IFLA_VTI_FLAGS])
+               flags = nla_get_u16(data[IFLA_VTI_FLAGS]);
+
+       parms->i_flags = vti_flags_to_tnl_flags(flags);
 }
 
 static int vti6_newlink(struct net *src_net, struct net_device *dev,
@@ -1084,6 +1108,8 @@ static size_t vti6_get_size(const struct net_device *dev)
                nla_total_size(4) +
                /* IFLA_VTI_FWMARK */
                nla_total_size(4) +
+               /* IFLA_VTI_FLAGS */
+               nla_total_size(2) +
                0;
 }
 
@@ -1097,7 +1123,9 @@ static int vti6_fill_info(struct sk_buff *skb, const 
struct net_device *dev)
            nla_put_in6_addr(skb, IFLA_VTI_REMOTE, &parm->raddr) ||
            nla_put_be32(skb, IFLA_VTI_IKEY, parm->i_key) ||
            nla_put_be32(skb, IFLA_VTI_OKEY, parm->o_key) ||
-           nla_put_u32(skb, IFLA_VTI_FWMARK, parm->fwmark))
+           nla_put_u32(skb, IFLA_VTI_FWMARK, parm->fwmark) ||
+           nla_put_u16(skb, IFLA_VTI_FLAGS,
+                       tnl_flags_to_vti_flags(parm->i_flags)))
                goto nla_put_failure;
        return 0;
 
@@ -1112,6 +1140,7 @@ static const struct nla_policy vti6_policy[IFLA_VTI_MAX + 
1] = {
        [IFLA_VTI_IKEY]         = { .type = NLA_U32 },
        [IFLA_VTI_OKEY]         = { .type = NLA_U32 },
        [IFLA_VTI_FWMARK]       = { .type = NLA_U32 },
+       [IFLA_VTI_FLAGS]        = { .type = NLA_U16 },
 };
 
 static struct rtnl_link_ops vti6_link_ops __read_mostly = {
-- 
2.15.1.620.gb9897f4670-goog

Reply via email to