1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Bridge per vlan tunnel port dst_metadata handling code |
4 | * |
5 | * Authors: |
6 | * Roopa Prabhu <roopa@cumulusnetworks.com> |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/netdevice.h> |
11 | #include <linux/rtnetlink.h> |
12 | #include <linux/slab.h> |
13 | #include <net/switchdev.h> |
14 | #include <net/dst_metadata.h> |
15 | |
16 | #include "br_private.h" |
17 | #include "br_private_tunnel.h" |
18 | |
19 | static inline int br_vlan_tunid_cmp(struct rhashtable_compare_arg *arg, |
20 | const void *ptr) |
21 | { |
22 | const struct net_bridge_vlan *vle = ptr; |
23 | __be64 tunid = *(__be64 *)arg->key; |
24 | |
25 | return vle->tinfo.tunnel_id != tunid; |
26 | } |
27 | |
28 | static const struct rhashtable_params br_vlan_tunnel_rht_params = { |
29 | .head_offset = offsetof(struct net_bridge_vlan, tnode), |
30 | .key_offset = offsetof(struct net_bridge_vlan, tinfo.tunnel_id), |
31 | .key_len = sizeof(__be64), |
32 | .nelem_hint = 3, |
33 | .obj_cmpfn = br_vlan_tunid_cmp, |
34 | .automatic_shrinking = true, |
35 | }; |
36 | |
37 | static struct net_bridge_vlan *br_vlan_tunnel_lookup(struct rhashtable *tbl, |
38 | __be64 tunnel_id) |
39 | { |
40 | return rhashtable_lookup_fast(ht: tbl, key: &tunnel_id, |
41 | params: br_vlan_tunnel_rht_params); |
42 | } |
43 | |
44 | static void vlan_tunnel_info_release(struct net_bridge_vlan *vlan) |
45 | { |
46 | struct metadata_dst *tdst = rtnl_dereference(vlan->tinfo.tunnel_dst); |
47 | |
48 | WRITE_ONCE(vlan->tinfo.tunnel_id, 0); |
49 | RCU_INIT_POINTER(vlan->tinfo.tunnel_dst, NULL); |
50 | dst_release(dst: &tdst->dst); |
51 | } |
52 | |
53 | void vlan_tunnel_info_del(struct net_bridge_vlan_group *vg, |
54 | struct net_bridge_vlan *vlan) |
55 | { |
56 | if (!rcu_access_pointer(vlan->tinfo.tunnel_dst)) |
57 | return; |
58 | rhashtable_remove_fast(ht: &vg->tunnel_hash, obj: &vlan->tnode, |
59 | params: br_vlan_tunnel_rht_params); |
60 | vlan_tunnel_info_release(vlan); |
61 | } |
62 | |
63 | static int __vlan_tunnel_info_add(struct net_bridge_vlan_group *vg, |
64 | struct net_bridge_vlan *vlan, u32 tun_id) |
65 | { |
66 | struct metadata_dst *metadata = rtnl_dereference(vlan->tinfo.tunnel_dst); |
67 | __be64 key = key32_to_tunnel_id(cpu_to_be32(tun_id)); |
68 | int err; |
69 | |
70 | if (metadata) |
71 | return -EEXIST; |
72 | |
73 | metadata = __ip_tun_set_dst(saddr: 0, daddr: 0, tos: 0, ttl: 0, tp_dst: 0, TUNNEL_KEY, |
74 | tunnel_id: key, md_size: 0); |
75 | if (!metadata) |
76 | return -EINVAL; |
77 | |
78 | metadata->u.tun_info.mode |= IP_TUNNEL_INFO_TX | IP_TUNNEL_INFO_BRIDGE; |
79 | rcu_assign_pointer(vlan->tinfo.tunnel_dst, metadata); |
80 | WRITE_ONCE(vlan->tinfo.tunnel_id, key); |
81 | |
82 | err = rhashtable_lookup_insert_fast(ht: &vg->tunnel_hash, obj: &vlan->tnode, |
83 | params: br_vlan_tunnel_rht_params); |
84 | if (err) |
85 | goto out; |
86 | |
87 | return 0; |
88 | out: |
89 | vlan_tunnel_info_release(vlan); |
90 | |
91 | return err; |
92 | } |
93 | |
94 | /* Must be protected by RTNL. |
95 | * Must be called with vid in range from 1 to 4094 inclusive. |
96 | */ |
97 | int nbp_vlan_tunnel_info_add(const struct net_bridge_port *port, u16 vid, |
98 | u32 tun_id) |
99 | { |
100 | struct net_bridge_vlan_group *vg; |
101 | struct net_bridge_vlan *vlan; |
102 | |
103 | ASSERT_RTNL(); |
104 | |
105 | vg = nbp_vlan_group(p: port); |
106 | vlan = br_vlan_find(vg, vid); |
107 | if (!vlan) |
108 | return -EINVAL; |
109 | |
110 | return __vlan_tunnel_info_add(vg, vlan, tun_id); |
111 | } |
112 | |
113 | /* Must be protected by RTNL. |
114 | * Must be called with vid in range from 1 to 4094 inclusive. |
115 | */ |
116 | int nbp_vlan_tunnel_info_delete(const struct net_bridge_port *port, u16 vid) |
117 | { |
118 | struct net_bridge_vlan_group *vg; |
119 | struct net_bridge_vlan *v; |
120 | |
121 | ASSERT_RTNL(); |
122 | |
123 | vg = nbp_vlan_group(p: port); |
124 | v = br_vlan_find(vg, vid); |
125 | if (!v) |
126 | return -ENOENT; |
127 | |
128 | vlan_tunnel_info_del(vg, vlan: v); |
129 | |
130 | return 0; |
131 | } |
132 | |
133 | static void __vlan_tunnel_info_flush(struct net_bridge_vlan_group *vg) |
134 | { |
135 | struct net_bridge_vlan *vlan, *tmp; |
136 | |
137 | list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist) |
138 | vlan_tunnel_info_del(vg, vlan); |
139 | } |
140 | |
141 | void nbp_vlan_tunnel_info_flush(struct net_bridge_port *port) |
142 | { |
143 | struct net_bridge_vlan_group *vg; |
144 | |
145 | ASSERT_RTNL(); |
146 | |
147 | vg = nbp_vlan_group(p: port); |
148 | __vlan_tunnel_info_flush(vg); |
149 | } |
150 | |
151 | int vlan_tunnel_init(struct net_bridge_vlan_group *vg) |
152 | { |
153 | return rhashtable_init(ht: &vg->tunnel_hash, params: &br_vlan_tunnel_rht_params); |
154 | } |
155 | |
156 | void vlan_tunnel_deinit(struct net_bridge_vlan_group *vg) |
157 | { |
158 | rhashtable_destroy(ht: &vg->tunnel_hash); |
159 | } |
160 | |
161 | void br_handle_ingress_vlan_tunnel(struct sk_buff *skb, |
162 | struct net_bridge_port *p, |
163 | struct net_bridge_vlan_group *vg) |
164 | { |
165 | struct ip_tunnel_info *tinfo = skb_tunnel_info(skb); |
166 | struct net_bridge_vlan *vlan; |
167 | |
168 | if (!vg || !tinfo) |
169 | return; |
170 | |
171 | /* if already tagged, ignore */ |
172 | if (skb_vlan_tagged(skb)) |
173 | return; |
174 | |
175 | /* lookup vid, given tunnel id */ |
176 | vlan = br_vlan_tunnel_lookup(tbl: &vg->tunnel_hash, tunnel_id: tinfo->key.tun_id); |
177 | if (!vlan) |
178 | return; |
179 | |
180 | skb_dst_drop(skb); |
181 | |
182 | __vlan_hwaccel_put_tag(skb, vlan_proto: p->br->vlan_proto, vlan_tci: vlan->vid); |
183 | } |
184 | |
185 | int br_handle_egress_vlan_tunnel(struct sk_buff *skb, |
186 | struct net_bridge_vlan *vlan) |
187 | { |
188 | struct metadata_dst *tunnel_dst; |
189 | __be64 tunnel_id; |
190 | int err; |
191 | |
192 | if (!vlan) |
193 | return 0; |
194 | |
195 | tunnel_id = READ_ONCE(vlan->tinfo.tunnel_id); |
196 | if (!tunnel_id || unlikely(!skb_vlan_tag_present(skb))) |
197 | return 0; |
198 | |
199 | skb_dst_drop(skb); |
200 | err = skb_vlan_pop(skb); |
201 | if (err) |
202 | return err; |
203 | |
204 | if (BR_INPUT_SKB_CB(skb)->backup_nhid) { |
205 | tunnel_dst = __ip_tun_set_dst(saddr: 0, daddr: 0, tos: 0, ttl: 0, tp_dst: 0, TUNNEL_KEY, |
206 | tunnel_id, md_size: 0); |
207 | if (!tunnel_dst) |
208 | return -ENOMEM; |
209 | |
210 | tunnel_dst->u.tun_info.mode |= IP_TUNNEL_INFO_TX | |
211 | IP_TUNNEL_INFO_BRIDGE; |
212 | tunnel_dst->u.tun_info.key.nhid = |
213 | BR_INPUT_SKB_CB(skb)->backup_nhid; |
214 | skb_dst_set(skb, dst: &tunnel_dst->dst); |
215 | |
216 | return 0; |
217 | } |
218 | |
219 | tunnel_dst = rcu_dereference(vlan->tinfo.tunnel_dst); |
220 | if (tunnel_dst && dst_hold_safe(dst: &tunnel_dst->dst)) |
221 | skb_dst_set(skb, dst: &tunnel_dst->dst); |
222 | |
223 | return 0; |
224 | } |
225 | |