162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Bridge per vlan tunnel port dst_metadata handling code 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Authors: 662306a36Sopenharmony_ci * Roopa Prabhu <roopa@cumulusnetworks.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/netdevice.h> 1162306a36Sopenharmony_ci#include <linux/rtnetlink.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include <net/switchdev.h> 1462306a36Sopenharmony_ci#include <net/dst_metadata.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include "br_private.h" 1762306a36Sopenharmony_ci#include "br_private_tunnel.h" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistatic inline int br_vlan_tunid_cmp(struct rhashtable_compare_arg *arg, 2062306a36Sopenharmony_ci const void *ptr) 2162306a36Sopenharmony_ci{ 2262306a36Sopenharmony_ci const struct net_bridge_vlan *vle = ptr; 2362306a36Sopenharmony_ci __be64 tunid = *(__be64 *)arg->key; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci return vle->tinfo.tunnel_id != tunid; 2662306a36Sopenharmony_ci} 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic const struct rhashtable_params br_vlan_tunnel_rht_params = { 2962306a36Sopenharmony_ci .head_offset = offsetof(struct net_bridge_vlan, tnode), 3062306a36Sopenharmony_ci .key_offset = offsetof(struct net_bridge_vlan, tinfo.tunnel_id), 3162306a36Sopenharmony_ci .key_len = sizeof(__be64), 3262306a36Sopenharmony_ci .nelem_hint = 3, 3362306a36Sopenharmony_ci .obj_cmpfn = br_vlan_tunid_cmp, 3462306a36Sopenharmony_ci .automatic_shrinking = true, 3562306a36Sopenharmony_ci}; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic struct net_bridge_vlan *br_vlan_tunnel_lookup(struct rhashtable *tbl, 3862306a36Sopenharmony_ci __be64 tunnel_id) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci return rhashtable_lookup_fast(tbl, &tunnel_id, 4162306a36Sopenharmony_ci br_vlan_tunnel_rht_params); 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic void vlan_tunnel_info_release(struct net_bridge_vlan *vlan) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci struct metadata_dst *tdst = rtnl_dereference(vlan->tinfo.tunnel_dst); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci WRITE_ONCE(vlan->tinfo.tunnel_id, 0); 4962306a36Sopenharmony_ci RCU_INIT_POINTER(vlan->tinfo.tunnel_dst, NULL); 5062306a36Sopenharmony_ci dst_release(&tdst->dst); 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_civoid vlan_tunnel_info_del(struct net_bridge_vlan_group *vg, 5462306a36Sopenharmony_ci struct net_bridge_vlan *vlan) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci if (!rcu_access_pointer(vlan->tinfo.tunnel_dst)) 5762306a36Sopenharmony_ci return; 5862306a36Sopenharmony_ci rhashtable_remove_fast(&vg->tunnel_hash, &vlan->tnode, 5962306a36Sopenharmony_ci br_vlan_tunnel_rht_params); 6062306a36Sopenharmony_ci vlan_tunnel_info_release(vlan); 6162306a36Sopenharmony_ci} 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic int __vlan_tunnel_info_add(struct net_bridge_vlan_group *vg, 6462306a36Sopenharmony_ci struct net_bridge_vlan *vlan, u32 tun_id) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci struct metadata_dst *metadata = rtnl_dereference(vlan->tinfo.tunnel_dst); 6762306a36Sopenharmony_ci __be64 key = key32_to_tunnel_id(cpu_to_be32(tun_id)); 6862306a36Sopenharmony_ci int err; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci if (metadata) 7162306a36Sopenharmony_ci return -EEXIST; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci metadata = __ip_tun_set_dst(0, 0, 0, 0, 0, TUNNEL_KEY, 7462306a36Sopenharmony_ci key, 0); 7562306a36Sopenharmony_ci if (!metadata) 7662306a36Sopenharmony_ci return -EINVAL; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci metadata->u.tun_info.mode |= IP_TUNNEL_INFO_TX | IP_TUNNEL_INFO_BRIDGE; 7962306a36Sopenharmony_ci rcu_assign_pointer(vlan->tinfo.tunnel_dst, metadata); 8062306a36Sopenharmony_ci WRITE_ONCE(vlan->tinfo.tunnel_id, key); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci err = rhashtable_lookup_insert_fast(&vg->tunnel_hash, &vlan->tnode, 8362306a36Sopenharmony_ci br_vlan_tunnel_rht_params); 8462306a36Sopenharmony_ci if (err) 8562306a36Sopenharmony_ci goto out; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci return 0; 8862306a36Sopenharmony_ciout: 8962306a36Sopenharmony_ci vlan_tunnel_info_release(vlan); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci return err; 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci/* Must be protected by RTNL. 9562306a36Sopenharmony_ci * Must be called with vid in range from 1 to 4094 inclusive. 9662306a36Sopenharmony_ci */ 9762306a36Sopenharmony_ciint nbp_vlan_tunnel_info_add(const struct net_bridge_port *port, u16 vid, 9862306a36Sopenharmony_ci u32 tun_id) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci struct net_bridge_vlan_group *vg; 10162306a36Sopenharmony_ci struct net_bridge_vlan *vlan; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci ASSERT_RTNL(); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci vg = nbp_vlan_group(port); 10662306a36Sopenharmony_ci vlan = br_vlan_find(vg, vid); 10762306a36Sopenharmony_ci if (!vlan) 10862306a36Sopenharmony_ci return -EINVAL; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci return __vlan_tunnel_info_add(vg, vlan, tun_id); 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci/* Must be protected by RTNL. 11462306a36Sopenharmony_ci * Must be called with vid in range from 1 to 4094 inclusive. 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_ciint nbp_vlan_tunnel_info_delete(const struct net_bridge_port *port, u16 vid) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci struct net_bridge_vlan_group *vg; 11962306a36Sopenharmony_ci struct net_bridge_vlan *v; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci ASSERT_RTNL(); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci vg = nbp_vlan_group(port); 12462306a36Sopenharmony_ci v = br_vlan_find(vg, vid); 12562306a36Sopenharmony_ci if (!v) 12662306a36Sopenharmony_ci return -ENOENT; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci vlan_tunnel_info_del(vg, v); 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci return 0; 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic void __vlan_tunnel_info_flush(struct net_bridge_vlan_group *vg) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci struct net_bridge_vlan *vlan, *tmp; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist) 13862306a36Sopenharmony_ci vlan_tunnel_info_del(vg, vlan); 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_civoid nbp_vlan_tunnel_info_flush(struct net_bridge_port *port) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci struct net_bridge_vlan_group *vg; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci ASSERT_RTNL(); 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci vg = nbp_vlan_group(port); 14862306a36Sopenharmony_ci __vlan_tunnel_info_flush(vg); 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ciint vlan_tunnel_init(struct net_bridge_vlan_group *vg) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci return rhashtable_init(&vg->tunnel_hash, &br_vlan_tunnel_rht_params); 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_civoid vlan_tunnel_deinit(struct net_bridge_vlan_group *vg) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci rhashtable_destroy(&vg->tunnel_hash); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_civoid br_handle_ingress_vlan_tunnel(struct sk_buff *skb, 16262306a36Sopenharmony_ci struct net_bridge_port *p, 16362306a36Sopenharmony_ci struct net_bridge_vlan_group *vg) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci struct ip_tunnel_info *tinfo = skb_tunnel_info(skb); 16662306a36Sopenharmony_ci struct net_bridge_vlan *vlan; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci if (!vg || !tinfo) 16962306a36Sopenharmony_ci return; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci /* if already tagged, ignore */ 17262306a36Sopenharmony_ci if (skb_vlan_tagged(skb)) 17362306a36Sopenharmony_ci return; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* lookup vid, given tunnel id */ 17662306a36Sopenharmony_ci vlan = br_vlan_tunnel_lookup(&vg->tunnel_hash, tinfo->key.tun_id); 17762306a36Sopenharmony_ci if (!vlan) 17862306a36Sopenharmony_ci return; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci skb_dst_drop(skb); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci __vlan_hwaccel_put_tag(skb, p->br->vlan_proto, vlan->vid); 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ciint br_handle_egress_vlan_tunnel(struct sk_buff *skb, 18662306a36Sopenharmony_ci struct net_bridge_vlan *vlan) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci struct metadata_dst *tunnel_dst; 18962306a36Sopenharmony_ci __be64 tunnel_id; 19062306a36Sopenharmony_ci int err; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci if (!vlan) 19362306a36Sopenharmony_ci return 0; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci tunnel_id = READ_ONCE(vlan->tinfo.tunnel_id); 19662306a36Sopenharmony_ci if (!tunnel_id || unlikely(!skb_vlan_tag_present(skb))) 19762306a36Sopenharmony_ci return 0; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci skb_dst_drop(skb); 20062306a36Sopenharmony_ci err = skb_vlan_pop(skb); 20162306a36Sopenharmony_ci if (err) 20262306a36Sopenharmony_ci return err; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci if (BR_INPUT_SKB_CB(skb)->backup_nhid) { 20562306a36Sopenharmony_ci tunnel_dst = __ip_tun_set_dst(0, 0, 0, 0, 0, TUNNEL_KEY, 20662306a36Sopenharmony_ci tunnel_id, 0); 20762306a36Sopenharmony_ci if (!tunnel_dst) 20862306a36Sopenharmony_ci return -ENOMEM; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci tunnel_dst->u.tun_info.mode |= IP_TUNNEL_INFO_TX | 21162306a36Sopenharmony_ci IP_TUNNEL_INFO_BRIDGE; 21262306a36Sopenharmony_ci tunnel_dst->u.tun_info.key.nhid = 21362306a36Sopenharmony_ci BR_INPUT_SKB_CB(skb)->backup_nhid; 21462306a36Sopenharmony_ci skb_dst_set(skb, &tunnel_dst->dst); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci return 0; 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci tunnel_dst = rcu_dereference(vlan->tinfo.tunnel_dst); 22062306a36Sopenharmony_ci if (tunnel_dst && dst_hold_safe(&tunnel_dst->dst)) 22162306a36Sopenharmony_ci skb_dst_set(skb, &tunnel_dst->dst); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci return 0; 22462306a36Sopenharmony_ci} 225