From: Roopa Prabhu <ro...@cumulusnetworks.com>

This patch adds support to attach per vlan tunnel info dst
metadata. This enables bridge driver to map vlan to tunnel_info
at ingress and egress

The initial use case is vlan to vni bridging, but the api is generic
to extend to any tunnel_info in the future:
    - Uapi to configure/unconfigure/dump per vlan tunnel data
    - netlink functions to configure vlan and tunnel_info mapping
    - Introduces bridge port flag BR_LWT_VLAN to enable attach/detach
    dst_metadata to bridged packets on ports.

Use case:
example use for this is a vxlan bridging gateway or vtep
which maps vlans to vn-segments (or vnis). User can configure
per-vlan tunnel information which the bridge driver can use
to bridge vlan into the corresponding tunnel.

CC: Nikolay Aleksandrov <niko...@cumulusnetworks.com>
Signed-off-by: Roopa Prabhu <ro...@cumulusnetworks.com>
---
CC'ing Nikolay for some more eyes as he has been trying to keep the
bridge driver fast path lite.

 include/linux/if_bridge.h |    1 +
 net/bridge/br_input.c     |    1 +
 net/bridge/br_netlink.c   |  410 ++++++++++++++++++++++++++++++++++++++-------
 net/bridge/br_private.h   |   18 ++
 net/bridge/br_vlan.c      |  138 ++++++++++++++-
 5 files changed, 507 insertions(+), 61 deletions(-)

diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
index c6587c0..36ff611 100644
--- a/include/linux/if_bridge.h
+++ b/include/linux/if_bridge.h
@@ -46,6 +46,7 @@ struct br_ip_list {
 #define BR_LEARNING_SYNC       BIT(9)
 #define BR_PROXYARP_WIFI       BIT(10)
 #define BR_MCAST_FLOOD         BIT(11)
+#define BR_LWT_VLAN            BIT(12)
 
 #define BR_DEFAULT_AGEING_TIME (300 * HZ)
 
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index 855b72f..83f356f 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -20,6 +20,7 @@
 #include <net/arp.h>
 #include <linux/export.h>
 #include <linux/rculist.h>
+#include <net/dst_metadata.h>
 #include "br_private.h"
 
 /* Hook for brouter */
diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
index 71c7453..df997ad 100644
--- a/net/bridge/br_netlink.c
+++ b/net/bridge/br_netlink.c
@@ -17,17 +17,30 @@
 #include <net/net_namespace.h>
 #include <net/sock.h>
 #include <uapi/linux/if_bridge.h>
+#include <net/dst_metadata.h>
 
 #include "br_private.h"
 #include "br_private_stp.h"
 
-static int __get_num_vlan_infos(struct net_bridge_vlan_group *vg,
-                               u32 filter_mask)
+static size_t br_get_vlan_tinfo_size(void)
 {
+       return nla_total_size(0) + /* nest IFLA_BRIDGE_VLAN_TUNNEL_INFO */
+                 nla_total_size(sizeof(u32)) + /* IFLA_BRIDGE_VLAN_TUNNEL_ID */
+                 nla_total_size(sizeof(u16)) + /* IFLA_BRIDGE_VLAN_TUNNEL_VID 
*/
+                 nla_total_size(sizeof(u16)); /* IFLA_BRIDGE_VLAN_TUNNEL_FLAGS 
*/
+}
+
+static int __get_num_vlan_infos(struct net_bridge_port *p,
+                               struct net_bridge_vlan_group *vg,
+                               u32 filter_mask, int *num_vtinfos)
+{
+       struct net_bridge_vlan *vbegin = NULL, *vend = NULL;
+       struct net_bridge_vlan *vtbegin = NULL, *vtend = NULL;
        struct net_bridge_vlan *v;
-       u16 vid_range_start = 0, vid_range_end = 0, vid_range_flags = 0;
+       bool get_tinfos = (p && p->flags & BR_LWT_VLAN) ? true: false;
+       bool vcontinue, vtcontinue;
+       int num_vinfos = 0;
        u16 flags, pvid;
-       int num_vlans = 0;
 
        if (!(filter_mask & RTEXT_FILTER_BRVLAN_COMPRESSED))
                return 0;
@@ -36,6 +49,8 @@ static int __get_num_vlan_infos(struct net_bridge_vlan_group 
*vg,
        /* Count number of vlan infos */
        list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
                flags = 0;
+               vcontinue = false;
+               vtcontinue = false;
                /* only a context, bridge vlan not activated */
                if (!br_vlan_should_use(v))
                        continue;
@@ -45,47 +60,79 @@ static int __get_num_vlan_infos(struct 
net_bridge_vlan_group *vg,
                if (v->flags & BRIDGE_VLAN_INFO_UNTAGGED)
                        flags |= BRIDGE_VLAN_INFO_UNTAGGED;
 
-               if (vid_range_start == 0) {
-                       goto initvars;
-               } else if ((v->vid - vid_range_end) == 1 &&
-                       flags == vid_range_flags) {
-                       vid_range_end = v->vid;
+               if (!vbegin) {
+                       vbegin = v;
+                       vend = v;
+                       vcontinue = true;
+               } else if ((v->vid - vend->vid) == 1 &&
+                       flags == vbegin->flags) {
+                       vend = v;
+                       vcontinue = true;
+               }
+
+               if (!vcontinue) {
+                       if ((vend->vid - vbegin->vid) > 0)
+                               num_vinfos += 2;
+                       else
+                               num_vinfos += 1;
+               }
+
+               if (!get_tinfos && !v->tinfo.tunnel_id)
                        continue;
-               } else {
-                       if ((vid_range_end - vid_range_start) > 0)
-                               num_vlans += 2;
+
+               if (!vtbegin) {
+                       vtbegin = v;
+                       vtend = v;
+                       vtcontinue = true;
+               } else if ((v->vid - vtend->vid) == 1 &&
+                   vlan_tunnel_id_isrange(vtend, v)) {
+                       vtend = v;
+                       vtcontinue = true;
+               }
+
+               if (!vtcontinue) {
+                       if ((vtend->vid - vtbegin->vid) > 0)
+                               num_vtinfos += 2;
                        else
-                               num_vlans += 1;
+                               num_vtinfos += 1;
+                       vbegin = NULL;
+                       vend = NULL;
                }
-initvars:
-               vid_range_start = v->vid;
-               vid_range_end = v->vid;
-               vid_range_flags = flags;
        }
 
-       if (vid_range_start != 0) {
-               if ((vid_range_end - vid_range_start) > 0)
-                       num_vlans += 2;
+       if (vbegin) {
+               if ((vend->vid - vbegin->vid) > 0)
+                       num_vinfos += 2;
                else
-                       num_vlans += 1;
+                       num_vinfos += 1;
        }
 
-       return num_vlans;
+       if (get_tinfos && vtbegin && vtbegin->tinfo.tunnel_id) {
+               if ((vtend->vid - vtbegin->vid) > 0)
+                       *num_vtinfos += 2;
+               else
+                       *num_vtinfos += 1;
+       }
+
+       return num_vinfos;
 }
 
-static int br_get_num_vlan_infos(struct net_bridge_vlan_group *vg,
-                                u32 filter_mask)
+static int br_get_num_vlan_infos(struct net_bridge_port *p,
+                                struct net_bridge_vlan_group *vg,
+                                int *num_tinfos, u32 filter_mask)
 {
        int num_vlans;
 
        if (!vg)
                return 0;
 
-       if (filter_mask & RTEXT_FILTER_BRVLAN)
+       if (filter_mask & RTEXT_FILTER_BRVLAN) {
+               *num_tinfos = vg->num_vlans;
                return vg->num_vlans;
+       }
 
        rcu_read_lock();
-       num_vlans = __get_num_vlan_infos(vg, filter_mask);
+       num_vlans = __get_num_vlan_infos(p, vg, filter_mask, num_tinfos);
        rcu_read_unlock();
 
        return num_vlans;
@@ -95,9 +142,10 @@ static size_t br_get_link_af_size_filtered(const struct 
net_device *dev,
                                           u32 filter_mask)
 {
        struct net_bridge_vlan_group *vg = NULL;
-       struct net_bridge_port *p;
+       struct net_bridge_port *p = NULL;
        struct net_bridge *br;
-       int num_vlan_infos;
+       int num_vlan_infos, num_vlan_tinfos = 0;
+       size_t retsize = 0;
 
        rcu_read_lock();
        if (br_port_exists(dev)) {
@@ -107,11 +155,15 @@ static size_t br_get_link_af_size_filtered(const struct 
net_device *dev,
                br = netdev_priv(dev);
                vg = br_vlan_group_rcu(br);
        }
-       num_vlan_infos = br_get_num_vlan_infos(vg, filter_mask);
+       num_vlan_infos = br_get_num_vlan_infos(p, vg, &num_vlan_tinfos,
+                                              filter_mask);
        rcu_read_unlock();
 
        /* Each VLAN is returned in bridge_vlan_info along with flags */
-       return num_vlan_infos * nla_total_size(sizeof(struct bridge_vlan_info));
+       retsize =  num_vlan_infos * nla_total_size(sizeof(struct 
bridge_vlan_info)) +
+                       num_vlan_tinfos * br_get_vlan_tinfo_size();
+
+       return retsize;
 }
 
 static inline size_t br_port_info_size(void)
@@ -191,7 +243,8 @@ static int br_port_fill_attrs(struct sk_buff *skb,
            nla_put_u16(skb, IFLA_BRPORT_NO, p->port_no) ||
            nla_put_u8(skb, IFLA_BRPORT_TOPOLOGY_CHANGE_ACK,
                       p->topology_change_ack) ||
-           nla_put_u8(skb, IFLA_BRPORT_CONFIG_PENDING, p->config_pending))
+           nla_put_u8(skb, IFLA_BRPORT_CONFIG_PENDING, p->config_pending) ||
+           nla_put_u8(skb, IFLA_BRPORT_LWT_VLAN, !!(p->flags & BR_LWT_VLAN)))
                return -EMSGSIZE;
 
        timerval = br_timer_value(&p->message_age_timer);
@@ -216,6 +269,34 @@ static int br_port_fill_attrs(struct sk_buff *skb,
        return 0;
 }
 
+static int br_fill_vlan_tinfo(struct sk_buff *skb, u16 vid,
+                              __be64 tunnel_id, u16 flags)
+{
+    __be32 tid = tunnel_id_to_key32(tunnel_id);
+    struct nlattr *tmap;
+
+       tmap = nla_nest_start(skb, IFLA_BRIDGE_VLAN_TUNNEL_INFO);
+       if (!tmap)
+               return -EMSGSIZE;
+       if (nla_put_u32(skb, IFLA_BRIDGE_VLAN_TUNNEL_ID,
+            be32_to_cpu(tid)))
+               goto nla_put_failure;
+       if (nla_put_u16(skb, IFLA_BRIDGE_VLAN_TUNNEL_VID,
+                       vid))
+               goto nla_put_failure;
+       if (nla_put_u16(skb, IFLA_BRIDGE_VLAN_TUNNEL_FLAGS,
+                       flags))
+               goto nla_put_failure;
+       nla_nest_end(skb, tmap);
+
+       return 0;
+
+nla_put_failure:
+       nla_nest_cancel(skb, tmap);
+
+       return -EMSGSIZE;
+}
+
 static int br_fill_ifvlaninfo_range(struct sk_buff *skb, u16 vid_start,
                                    u16 vid_end, u16 flags)
 {
@@ -249,20 +330,24 @@ static int br_fill_ifvlaninfo_range(struct sk_buff *skb, 
u16 vid_start,
 }
 
 static int br_fill_ifvlaninfo_compressed(struct sk_buff *skb,
+                                        struct net_bridge_port *p,
                                         struct net_bridge_vlan_group *vg)
 {
+       struct net_bridge_vlan *vbegin = NULL, *vend = NULL;
+       struct net_bridge_vlan *vtbegin = NULL, *vtend = NULL;
+       bool fill_tinfos = (p && p->flags & BR_LWT_VLAN) ? true: false;
        struct net_bridge_vlan *v;
-       u16 vid_range_start = 0, vid_range_end = 0, vid_range_flags = 0;
+       bool vcontinue, vtcontinue;
        u16 flags, pvid;
-       int err = 0;
+       int err;
 
-       /* Pack IFLA_BRIDGE_VLAN_INFO's for every vlan
-        * and mark vlan info with begin and end flags
-        * if vlaninfo represents a range
-        */
        pvid = br_get_pvid(vg);
+       /* Count number of vlan infos */
        list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
                flags = 0;
+               vcontinue = false;
+               vtcontinue = false;
+               /* only a context, bridge vlan not activated */
                if (!br_vlan_should_use(v))
                        continue;
                if (v->vid == pvid)
@@ -271,44 +356,103 @@ static int br_fill_ifvlaninfo_compressed(struct sk_buff 
*skb,
                if (v->flags & BRIDGE_VLAN_INFO_UNTAGGED)
                        flags |= BRIDGE_VLAN_INFO_UNTAGGED;
 
-               if (vid_range_start == 0) {
-                       goto initvars;
-               } else if ((v->vid - vid_range_end) == 1 &&
-                       flags == vid_range_flags) {
-                       vid_range_end = v->vid;
-                       continue;
-               } else {
-                       err = br_fill_ifvlaninfo_range(skb, vid_range_start,
-                                                      vid_range_end,
-                                                      vid_range_flags);
+               if (!vbegin) {
+                       vbegin = v;
+                       vend = v;
+                       vcontinue = true;
+               } else if ((v->vid - vend->vid) == 1 &&
+                       flags == vbegin->flags) {
+                       vend = v;
+                       vcontinue = true;
+               }
+
+               if (!vcontinue) {
+                       err = br_fill_ifvlaninfo_range(skb,
+                                                      vbegin->vid,
+                                                      vend->vid,
+                                                      vbegin->flags);
                        if (err)
                                return err;
+                       vbegin = vend = NULL;
+               }
+
+               if (!fill_tinfos || !v->tinfo.tunnel_id)
+                       continue;
+
+               if (!vtbegin) {
+                       vtbegin = v;
+                       vtend = v;
+                       vtcontinue = true;
+               } else if ((v->vid - vtend->vid) == 1 &&
+                   vlan_tunnel_id_isrange(vtend, v)) {
+                       vtend = v;
+                       vtcontinue = true;
                }
 
-initvars:
-               vid_range_start = v->vid;
-               vid_range_end = v->vid;
-               vid_range_flags = flags;
+               if (!vtcontinue && vtbegin->tinfo.tunnel_id) {
+                       if ((vtend->vid - vtbegin->vid) > 0) {
+                               err = br_fill_vlan_tinfo(skb, vbegin->vid,
+                                                        
vbegin->tinfo.tunnel_id,
+                                               BRIDGE_VLAN_INFO_RANGE_BEGIN);
+                               if (err)
+                                       return err;
+                               err = br_fill_vlan_tinfo(skb, vend->vid,
+                                                        vend->tinfo.tunnel_id,
+                                               BRIDGE_VLAN_INFO_RANGE_END);
+                               if (err)
+                                       return err;
+                       } else {
+                               err = br_fill_vlan_tinfo(skb, vbegin->vid,
+                                                        
vbegin->tinfo.tunnel_id,
+                                                        0);
+                               if (err)
+                                       return err;
+                       }
+                       vbegin = NULL;
+                       vend = NULL;
+               }
        }
 
-       if (vid_range_start != 0) {
-               /* Call it once more to send any left over vlans */
-               err = br_fill_ifvlaninfo_range(skb, vid_range_start,
-                                              vid_range_end,
-                                              vid_range_flags);
+       if (vbegin) {
+               err = br_fill_ifvlaninfo_range(skb, vbegin->vid,
+                                              vend->vid,
+                                              vbegin->flags);
                if (err)
                        return err;
        }
 
+       if (fill_tinfos && vtbegin && vtbegin->tinfo.tunnel_id) {
+               if ((vtend->vid - vtbegin->vid) > 0) {
+                       err = br_fill_vlan_tinfo(skb, vbegin->vid,
+                                                vbegin->tinfo.tunnel_id,
+                                                BRIDGE_VLAN_INFO_RANGE_BEGIN);
+                       if (err)
+                               return err;
+                       err = br_fill_vlan_tinfo(skb, vend->vid,
+                                                vend->tinfo.tunnel_id,
+                                                BRIDGE_VLAN_INFO_RANGE_END);
+                       if (err)
+                               return err;
+               } else {
+                       err = br_fill_vlan_tinfo(skb, vbegin->vid,
+                                                vbegin->tinfo.tunnel_id, 0);
+                       if (err)
+                               return err;
+               }
+       }
+
        return 0;
 }
 
 static int br_fill_ifvlaninfo(struct sk_buff *skb,
+                             struct net_bridge_port *p,
                              struct net_bridge_vlan_group *vg)
 {
        struct bridge_vlan_info vinfo;
        struct net_bridge_vlan *v;
+       bool fill_tinfos = (p && p->flags & BR_LWT_VLAN) ? true : false;
        u16 pvid;
+       int err;
 
        pvid = br_get_pvid(vg);
        list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
@@ -326,6 +470,14 @@ static int br_fill_ifvlaninfo(struct sk_buff *skb,
                if (nla_put(skb, IFLA_BRIDGE_VLAN_INFO,
                            sizeof(vinfo), &vinfo))
                        goto nla_put_failure;
+
+               if (!fill_tinfos || !v->tinfo.tunnel_id)
+                       continue;
+
+               err = br_fill_vlan_tinfo(skb, v->vid,
+                                        v->tinfo.tunnel_id, 0);
+               if (err)
+                       return err;
        }
 
        return 0;
@@ -411,9 +563,9 @@ static int br_fill_ifinfo(struct sk_buff *skb,
                        goto nla_put_failure;
                }
                if (filter_mask & RTEXT_FILTER_BRVLAN_COMPRESSED)
-                       err = br_fill_ifvlaninfo_compressed(skb, vg);
+                       err = br_fill_ifvlaninfo_compressed(skb, port, vg);
                else
-                       err = br_fill_ifvlaninfo(skb, vg);
+                       err = br_fill_ifvlaninfo(skb, port, vg);
                rcu_read_unlock();
                if (err)
                        goto nla_put_failure;
@@ -514,6 +666,127 @@ static int br_vlan_info(struct net_bridge *br, struct 
net_bridge_port *p,
        return err;
 }
 
+static const struct nla_policy vlan_tunnel_policy[IFLA_BRIDGE_VLAN_TUNNEL_MAX 
+ 1] = {
+       [IFLA_BRIDGE_VLAN_TUNNEL_ID]= { .type = NLA_U32 },
+       [IFLA_BRIDGE_VLAN_TUNNEL_VID] = { .type = NLA_U16 },
+       [IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] = { .type = NLA_U16 },
+};
+
+static int br_add_vlan_tunnel_info(struct net_bridge *br,
+                                  struct net_bridge_port *p, int cmd,
+                                  u16 vid, u32 tun_id)
+{
+       int err;
+
+       switch (cmd) {
+       case RTM_SETLINK:
+               if (p) {
+                       /* if the MASTER flag is set this will act on the global
+                        * per-VLAN entry as well
+                        */
+                       err = nbp_vlan_tunnel_info_add(p, vid, tun_id);
+                       if (err)
+                               break;
+               } else {
+                       return -EINVAL;
+               }
+
+               break;
+
+       case RTM_DELLINK:
+               if (p)
+                       nbp_vlan_tunnel_info_delete(p, vid);
+               else
+                       return -EINVAL;
+               break;
+       }
+
+       return 0;
+}
+
+struct vtunnel_info {
+       u32     tunid;
+       u16     vid;
+       u16     flags;
+};
+
+static int br_parse_vlan_tunnel_info(struct nlattr *attr,
+                                    struct vtunnel_info *tinfo)
+{
+       struct nlattr *tb[IFLA_BRIDGE_VLAN_TUNNEL_MAX + 1];
+       u32 tun_id;
+       u16 vid, flags;
+       int err;
+
+       err = nla_parse_nested(tb, IFLA_BRIDGE_VLAN_TUNNEL_MAX,
+                              attr, vlan_tunnel_policy);
+       if (err < 0)
+               return err;
+
+       if (tb[IFLA_BRIDGE_VLAN_TUNNEL_ID])
+               tun_id = nla_get_u32(tb[IFLA_BRIDGE_VLAN_TUNNEL_ID]);
+       else
+               return -EINVAL;
+
+       if (tb[IFLA_BRIDGE_VLAN_TUNNEL_VID]) {
+               vid = nla_get_u16(tb[IFLA_BRIDGE_VLAN_TUNNEL_VID]);
+               if (vid >= VLAN_VID_MASK)
+                       return -ERANGE;
+       } else {
+               return -EINVAL;
+       }
+
+       if (tb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS])
+               flags = nla_get_u16(tb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]);
+
+       tinfo->tunid = tun_id;
+       tinfo->vid = vid;
+       tinfo->flags = flags;
+
+       return 0;
+}
+
+static int br_process_vlan_tunnel_info(struct net_bridge *br,
+                                      struct net_bridge_port *p, int cmd,
+                                      struct vtunnel_info *tinfo_curr,
+                                      struct vtunnel_info *tinfo_last)
+{
+       int t, v;
+       int err;
+
+       if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
+               if (tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN)
+                       return -EINVAL;
+               memcpy(tinfo_last, tinfo_curr, sizeof(struct vtunnel_info));
+       } else if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_END) {
+               if (!(tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN))
+                       return -EINVAL;
+               if ((tinfo_curr->vid - tinfo_last->vid) !=
+                   (tinfo_curr->tunid - tinfo_last->tunid))
+                       return -EINVAL;
+               /* XXX: tun id and vlan id attrs must be same
+                */
+               t = tinfo_last->tunid;
+               for (v = tinfo_last->vid; v <= tinfo_curr->vid; v++) {
+                       err = br_add_vlan_tunnel_info(br, p, cmd,
+                                                         v, t);
+                       if (err)
+                               return err;
+                       t++;
+               }
+               memset(tinfo_last, 0, sizeof(struct vtunnel_info));
+               memset(tinfo_curr, 0, sizeof(struct vtunnel_info));
+       } else {
+               err = br_add_vlan_tunnel_info(br, p, cmd,
+                                             tinfo_curr->vid,
+                                             tinfo_curr->tunid);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
 static int br_afspec(struct net_bridge *br,
                     struct net_bridge_port *p,
                     struct nlattr *af_spec,
@@ -522,10 +795,30 @@ static int br_afspec(struct net_bridge *br,
        struct bridge_vlan_info *vinfo_start = NULL;
        struct bridge_vlan_info *vinfo = NULL;
        struct nlattr *attr;
+       struct vtunnel_info tinfo_last = {
+               .tunid = 0,
+               .vid = 0,
+               .flags = 0};
+       struct vtunnel_info tinfo_curr = {
+               .tunid = 0,
+               .vid = 0,
+               .flags = 0};
        int err = 0;
        int rem;
 
        nla_for_each_nested(attr, af_spec, rem) {
+               if (nla_type(attr) == IFLA_BRIDGE_VLAN_TUNNEL_INFO) {
+                       err = br_parse_vlan_tunnel_info(attr, &tinfo_curr);
+                       if (err)
+                               return err;
+                       err = br_process_vlan_tunnel_info(br, p, cmd,
+                                                         &tinfo_curr,
+                                                         &tinfo_last);
+                       if (err)
+                               return err;
+                       continue;
+               }
+
                if (nla_type(attr) != IFLA_BRIDGE_VLAN_INFO)
                        continue;
                if (nla_len(attr) != sizeof(struct bridge_vlan_info))
@@ -638,6 +931,7 @@ static int br_setport(struct net_bridge_port *p, struct 
nlattr *tb[])
        br_set_port_flag(p, tb, IFLA_BRPORT_MCAST_FLOOD, BR_MCAST_FLOOD);
        br_set_port_flag(p, tb, IFLA_BRPORT_PROXYARP, BR_PROXYARP);
        br_set_port_flag(p, tb, IFLA_BRPORT_PROXYARP_WIFI, BR_PROXYARP_WIFI);
+       br_set_port_flag(p, tb, IFLA_BRPORT_LWT_VLAN, BR_LWT_VLAN);
 
        if (tb[IFLA_BRPORT_COST]) {
                err = br_stp_set_path_cost(p, 
nla_get_u32(tb[IFLA_BRPORT_COST]));
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 8ce621e..f68e360 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -91,6 +91,11 @@ struct br_vlan_stats {
        struct u64_stats_sync syncp;
 };
 
+struct br_tunnel_info {
+       __be64                  tunnel_id;
+       struct metadata_dst     *tunnel_dst;
+};
+
 /**
  * struct net_bridge_vlan - per-vlan entry
  *
@@ -113,6 +118,7 @@ struct br_vlan_stats {
  */
 struct net_bridge_vlan {
        struct rhash_head               vnode;
+       struct rhash_head               tnode;
        u16                             vid;
        u16                             flags;
        struct br_vlan_stats __percpu   *stats;
@@ -124,6 +130,9 @@ struct net_bridge_vlan {
                atomic_t                refcnt;
                struct net_bridge_vlan  *brvlan;
        };
+
+       struct br_tunnel_info           tinfo;
+
        struct list_head                vlist;
 
        struct rcu_head                 rcu;
@@ -145,6 +154,7 @@ struct net_bridge_vlan {
  */
 struct net_bridge_vlan_group {
        struct rhashtable               vlan_hash;
+       struct rhashtable               tunnel_hash;
        struct list_head                vlan_list;
        u16                             num_vlans;
        u16                             pvid;
@@ -786,6 +796,14 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br,
 int nbp_get_num_vlan_infos(struct net_bridge_port *p, u32 filter_mask);
 void br_vlan_get_stats(const struct net_bridge_vlan *v,
                       struct br_vlan_stats *stats);
+int __vlan_tunnel_info_add(struct net_bridge_vlan_group *vg,
+                          struct net_bridge_vlan *vlan, u32 tun_id);
+int __vlan_tunnel_info_del(struct net_bridge_vlan_group *vg,
+                           struct net_bridge_vlan *vlan);
+int nbp_vlan_tunnel_info_delete(struct net_bridge_port *port, u16 vid);
+int nbp_vlan_tunnel_info_add(struct net_bridge_port *port, u16 vid, u32 
tun_id);
+bool vlan_tunnel_id_isrange(struct net_bridge_vlan *v_end,
+                           struct net_bridge_vlan *v);
 
 static inline struct net_bridge_vlan_group *br_vlan_group(
                                        const struct net_bridge *br)
diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
index b6de4f4..2040f08 100644
--- a/net/bridge/br_vlan.c
+++ b/net/bridge/br_vlan.c
@@ -3,6 +3,7 @@
 #include <linux/rtnetlink.h>
 #include <linux/slab.h>
 #include <net/switchdev.h>
+#include <net/dst_metadata.h>
 
 #include "br_private.h"
 
@@ -31,6 +32,31 @@ static struct net_bridge_vlan *br_vlan_lookup(struct 
rhashtable *tbl, u16 vid)
        return rhashtable_lookup_fast(tbl, &vid, br_vlan_rht_params);
 }
 
+static inline int br_vlan_tunid_cmp(struct rhashtable_compare_arg *arg,
+                                   const void *ptr)
+{
+       const struct net_bridge_vlan *vle = ptr;
+       __be64 tunid = *(__be64 *)arg->key;
+
+       return vle->tinfo.tunnel_id != tunid;
+}
+
+static const struct rhashtable_params br_vlan_tunnel_rht_params = {
+       .head_offset = offsetof(struct net_bridge_vlan, tnode),
+       .key_offset = offsetof(struct net_bridge_vlan, tinfo.tunnel_id),
+       .key_len = sizeof(__be64),
+       .nelem_hint = 3,
+       .locks_mul = 1,
+       .obj_cmpfn = br_vlan_tunid_cmp,
+       .automatic_shrinking = true,
+};
+
+static struct net_bridge_vlan *br_vlan_tunnel_lookup(struct rhashtable *tbl,
+                                                    u64 tunnel_id)
+{
+       return rhashtable_lookup_fast(tbl, &tunnel_id, 
br_vlan_tunnel_rht_params);
+}
+
 static void __vlan_add_pvid(struct net_bridge_vlan_group *vg, u16 vid)
 {
        if (vg->pvid == vid)
@@ -325,6 +351,7 @@ static void __vlan_group_free(struct net_bridge_vlan_group 
*vg)
 {
        WARN_ON(!list_empty(&vg->vlan_list));
        rhashtable_destroy(&vg->vlan_hash);
+       rhashtable_destroy(&vg->tunnel_hash);
        kfree(vg);
 }
 
@@ -613,6 +640,8 @@ int br_vlan_delete(struct net_bridge *br, u16 vid)
        br_fdb_find_delete_local(br, NULL, br->dev->dev_addr, vid);
        br_fdb_delete_by_port(br, NULL, vid, 0);
 
+       __vlan_tunnel_info_del(vg, v);
+
        return __vlan_del(v);
 }
 
@@ -918,6 +947,9 @@ int br_vlan_init(struct net_bridge *br)
        ret = rhashtable_init(&vg->vlan_hash, &br_vlan_rht_params);
        if (ret)
                goto err_rhtbl;
+       ret = rhashtable_init(&vg->tunnel_hash, &br_vlan_tunnel_rht_params);
+       if (ret)
+               goto err_rhtbl2;
        INIT_LIST_HEAD(&vg->vlan_list);
        br->vlan_proto = htons(ETH_P_8021Q);
        br->default_pvid = 1;
@@ -932,6 +964,8 @@ int br_vlan_init(struct net_bridge *br)
        return ret;
 
 err_vlan_add:
+       rhashtable_destroy(&vg->tunnel_hash);
+err_rhtbl2:
        rhashtable_destroy(&vg->vlan_hash);
 err_rhtbl:
        kfree(vg);
@@ -960,7 +994,10 @@ int nbp_vlan_init(struct net_bridge_port *p)
 
        ret = rhashtable_init(&vg->vlan_hash, &br_vlan_rht_params);
        if (ret)
-               goto err_rhtbl;
+               goto err_rhtbl1;
+       ret = rhashtable_init(&vg->tunnel_hash, &br_vlan_tunnel_rht_params);
+       if (ret)
+               goto err_rhtbl2;
        INIT_LIST_HEAD(&vg->vlan_list);
        rcu_assign_pointer(p->vlgrp, vg);
        if (p->br->default_pvid) {
@@ -976,9 +1013,11 @@ int nbp_vlan_init(struct net_bridge_port *p)
 err_vlan_add:
        RCU_INIT_POINTER(p->vlgrp, NULL);
        synchronize_rcu();
-       rhashtable_destroy(&vg->vlan_hash);
+       rhashtable_destroy(&vg->tunnel_hash);
 err_vlan_enabled:
-err_rhtbl:
+err_rhtbl2:
+       rhashtable_destroy(&vg->vlan_hash);
+err_rhtbl1:
        kfree(vg);
 
        goto out;
@@ -1081,3 +1120,96 @@ void br_vlan_get_stats(const struct net_bridge_vlan *v,
                stats->tx_packets += txpackets;
        }
 }
+
+bool vlan_tunnel_id_isrange(struct net_bridge_vlan *v_end,
+                           struct net_bridge_vlan *v)
+{
+       /* XXX: check other tunnel attributes */
+       return (be32_to_cpu(tunnel_id_to_key32(v_end->tinfo.tunnel_id)) -
+               be32_to_cpu(tunnel_id_to_key32(v->tinfo.tunnel_id)) == 1);
+}
+
+int __vlan_tunnel_info_add(struct net_bridge_vlan_group *vg,
+                          struct net_bridge_vlan *vlan, u32 tun_id)
+{
+       struct metadata_dst *metadata = NULL;
+       __be64 key = key32_to_tunnel_id(cpu_to_be32(tun_id));
+       int err;
+
+       if (vlan->tinfo.tunnel_dst)
+               return -EEXIST;
+
+       metadata = __ip_tun_set_dst(0, 0, 0, 0, 0, TUNNEL_KEY,
+                                   key, 0);
+       if (!metadata)
+               return -EINVAL;
+
+       metadata->u.tun_info.mode |= IP_TUNNEL_INFO_TX | IP_TUNNEL_INFO_BRIDGE;
+       vlan->tinfo.tunnel_dst = metadata;
+       vlan->tinfo.tunnel_id = key;
+
+       err = rhashtable_lookup_insert_fast(&vg->tunnel_hash, &vlan->tnode,
+                                           br_vlan_tunnel_rht_params);
+       if (err)
+               goto out;
+
+       return 0;
+out:
+       dst_release(&vlan->tinfo.tunnel_dst->dst);
+
+       return err;
+}
+
+int __vlan_tunnel_info_del(struct net_bridge_vlan_group *vg,
+                          struct net_bridge_vlan *vlan)
+{
+       if (vlan->tinfo.tunnel_dst) {
+               vlan->tinfo.tunnel_id = 0;
+               dst_release(&vlan->tinfo.tunnel_dst->dst);
+
+               rhashtable_remove_fast(&vg->tunnel_hash, &vlan->vnode,
+                                      br_vlan_tunnel_rht_params);
+       }
+
+       return 0;
+}
+
+/* Must be protected by RTNL.
+ * Must be called with vid in range from 1 to 4094 inclusive.
+ */
+int nbp_vlan_tunnel_info_add(struct net_bridge_port *port, u16 vid, u32 tun_id)
+{
+       struct net_bridge_vlan_group *vg;
+       struct net_bridge_vlan *vlan;
+
+       ASSERT_RTNL();
+
+       vg = nbp_vlan_group(port);
+       vlan = br_vlan_find(vg, vid);
+       if (!vlan)
+               return -EINVAL;
+
+       __vlan_tunnel_info_add(vg, vlan, tun_id);
+
+       return 0;
+}
+
+/* Must be protected by RTNL.
+ * Must be called with vid in range from 1 to 4094 inclusive.
+ */
+int nbp_vlan_tunnel_info_delete(struct net_bridge_port *port, u16 vid)
+{
+       struct net_bridge_vlan_group *vg;
+       struct net_bridge_vlan *v;
+
+       ASSERT_RTNL();
+
+       vg = nbp_vlan_group(port);
+       v = br_vlan_find(vg, vid);
+       if (!v)
+               return -ENOENT;
+
+       __vlan_tunnel_info_del(vg, v);
+
+       return 0;
+}
-- 
1.7.10.4

Reply via email to