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