1/*
2 * gbp.h : Group Based Policy
3 *
4 * Copyright (c) 2018 Cisco and/or its affiliates.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at:
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#include <plugins/gbp/gbp_endpoint_group.h>
19#include <plugins/gbp/gbp_endpoint.h>
20#include <plugins/gbp/gbp_bridge_domain.h>
21#include <plugins/gbp/gbp_route_domain.h>
22#include <plugins/gbp/gbp_itf.h>
23
24#include <vnet/dpo/dvr_dpo.h>
25#include <vnet/fib/fib_table.h>
26#include <vnet/l2/l2_input.h>
27
28/**
29 * Pool of GBP endpoint_groups
30 */
31gbp_endpoint_group_t *gbp_endpoint_group_pool;
32
33/**
34 * DB of endpoint_groups
35 */
36gbp_endpoint_group_db_t gbp_endpoint_group_db;
37
38/**
39 * Map sclass to EPG
40 */
41uword *gbp_epg_sclass_db;
42
43vlib_log_class_t gg_logger;
44
45#define GBP_EPG_DBG(...)                           \
46    vlib_log_debug (gg_logger, __VA_ARGS__);
47
48gbp_endpoint_group_t *
49gbp_endpoint_group_get (index_t i)
50{
51  return (pool_elt_at_index (gbp_endpoint_group_pool, i));
52}
53
54void
55gbp_endpoint_group_lock (index_t ggi)
56{
57  gbp_endpoint_group_t *gg;
58
59  if (INDEX_INVALID == ggi)
60    return;
61
62  gg = gbp_endpoint_group_get (ggi);
63  gg->gg_locks++;
64}
65
66index_t
67gbp_endpoint_group_find (sclass_t sclass)
68{
69  uword *p;
70
71  p = hash_get (gbp_endpoint_group_db.gg_hash_sclass, sclass);
72
73  if (NULL != p)
74    return p[0];
75
76  return (INDEX_INVALID);
77}
78
79int
80gbp_endpoint_group_add_and_lock (vnid_t vnid,
81				 u16 sclass,
82				 u32 bd_id,
83				 u32 rd_id,
84				 u32 uplink_sw_if_index,
85				 const gbp_endpoint_retention_t * retention)
86{
87  gbp_endpoint_group_t *gg;
88  index_t ggi;
89
90  ggi = gbp_endpoint_group_find (sclass);
91
92  if (INDEX_INVALID == ggi)
93    {
94      fib_protocol_t fproto;
95      index_t gbi, grdi;
96
97      gbi = gbp_bridge_domain_find_and_lock (bd_id);
98
99      if (~0 == gbi)
100	return (VNET_API_ERROR_BD_NOT_MODIFIABLE);
101
102      grdi = gbp_route_domain_find_and_lock (rd_id);
103
104      if (~0 == grdi)
105	{
106	  gbp_bridge_domain_unlock (gbi);
107	  return (VNET_API_ERROR_NO_SUCH_FIB);
108	}
109
110      pool_get_zero (gbp_endpoint_group_pool, gg);
111
112      gg->gg_vnid = vnid;
113      gg->gg_rd = grdi;
114      gg->gg_gbd = gbi;
115
116      gg->gg_uplink_sw_if_index = uplink_sw_if_index;
117      gbp_itf_hdl_reset (&gg->gg_uplink_itf);
118      gg->gg_locks = 1;
119      gg->gg_sclass = sclass;
120      gg->gg_retention = *retention;
121
122      if (SCLASS_INVALID != gg->gg_sclass)
123	hash_set (gbp_epg_sclass_db, gg->gg_sclass, gg->gg_vnid);
124
125      /*
126       * an egress DVR dpo for internal subnets to use when sending
127       * on the uplink interface
128       */
129      if (~0 != gg->gg_uplink_sw_if_index)
130	{
131	  FOR_EACH_FIB_IP_PROTOCOL (fproto)
132	  {
133	    dvr_dpo_add_or_lock (uplink_sw_if_index,
134				 fib_proto_to_dpo (fproto),
135				 &gg->gg_dpo[fproto]);
136	  }
137
138	  /*
139	   * Add the uplink to the BD
140	   * packets direct from the uplink have had policy applied
141	   */
142	  gg->gg_uplink_itf =
143	    gbp_itf_l2_add_and_lock (gg->gg_uplink_sw_if_index, gbi);
144
145	  gbp_itf_l2_set_input_feature (gg->gg_uplink_itf,
146					L2INPUT_FEAT_GBP_NULL_CLASSIFY);
147	}
148
149      hash_set (gbp_endpoint_group_db.gg_hash_sclass,
150		gg->gg_sclass, gg - gbp_endpoint_group_pool);
151    }
152  else
153    {
154      gg = gbp_endpoint_group_get (ggi);
155      gg->gg_locks++;
156    }
157
158  GBP_EPG_DBG ("add: %U", format_gbp_endpoint_group, gg);
159
160  return (0);
161}
162
163void
164gbp_endpoint_group_unlock (index_t ggi)
165{
166  gbp_endpoint_group_t *gg;
167
168  if (INDEX_INVALID == ggi)
169    return;
170
171  gg = gbp_endpoint_group_get (ggi);
172
173  gg->gg_locks--;
174
175  if (0 == gg->gg_locks)
176    {
177      fib_protocol_t fproto;
178
179      gg = pool_elt_at_index (gbp_endpoint_group_pool, ggi);
180
181      gbp_itf_unlock (&gg->gg_uplink_itf);
182
183      FOR_EACH_FIB_IP_PROTOCOL (fproto)
184      {
185	dpo_reset (&gg->gg_dpo[fproto]);
186      }
187      gbp_bridge_domain_unlock (gg->gg_gbd);
188      gbp_route_domain_unlock (gg->gg_rd);
189
190      if (SCLASS_INVALID != gg->gg_sclass)
191	hash_unset (gbp_epg_sclass_db, gg->gg_sclass);
192      hash_unset (gbp_endpoint_group_db.gg_hash_sclass, gg->gg_sclass);
193
194      pool_put (gbp_endpoint_group_pool, gg);
195    }
196}
197
198int
199gbp_endpoint_group_delete (sclass_t sclass)
200{
201  index_t ggi;
202
203  ggi = gbp_endpoint_group_find (sclass);
204
205  if (INDEX_INVALID != ggi)
206    {
207      GBP_EPG_DBG ("del: %U", format_gbp_endpoint_group,
208		   gbp_endpoint_group_get (ggi));
209      gbp_endpoint_group_unlock (ggi);
210
211      return (0);
212    }
213
214  return (VNET_API_ERROR_NO_SUCH_ENTRY);
215}
216
217u32
218gbp_endpoint_group_get_bd_id (const gbp_endpoint_group_t * gg)
219{
220  const gbp_bridge_domain_t *gb;
221
222  gb = gbp_bridge_domain_get (gg->gg_gbd);
223
224  return (gb->gb_bd_id);
225}
226
227index_t
228gbp_endpoint_group_get_fib_index (const gbp_endpoint_group_t * gg,
229				  fib_protocol_t fproto)
230{
231  const gbp_route_domain_t *grd;
232
233  grd = gbp_route_domain_get (gg->gg_rd);
234
235  return (grd->grd_fib_index[fproto]);
236}
237
238void
239gbp_endpoint_group_walk (gbp_endpoint_group_cb_t cb, void *ctx)
240{
241  gbp_endpoint_group_t *gbpe;
242
243  /* *INDENT-OFF* */
244  pool_foreach(gbpe, gbp_endpoint_group_pool,
245  {
246    if (!cb(gbpe, ctx))
247      break;
248  });
249  /* *INDENT-ON* */
250}
251
252static clib_error_t *
253gbp_endpoint_group_cli (vlib_main_t * vm,
254			unformat_input_t * input, vlib_cli_command_t * cmd)
255{
256  gbp_endpoint_retention_t retention = { 0 };
257  vnid_t vnid = VNID_INVALID, sclass;
258  vnet_main_t *vnm = vnet_get_main ();
259  u32 uplink_sw_if_index = ~0;
260  u32 bd_id = ~0;
261  u32 rd_id = ~0;
262  u8 add = 1;
263
264  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
265    {
266      if (unformat (input, "%U", unformat_vnet_sw_interface,
267		    vnm, &uplink_sw_if_index))
268	;
269      else if (unformat (input, "add"))
270	add = 1;
271      else if (unformat (input, "del"))
272	add = 0;
273      else if (unformat (input, "epg %d", &vnid))
274	;
275      else if (unformat (input, "sclass %d", &sclass))
276	;
277      else if (unformat (input, "bd %d", &bd_id))
278	;
279      else if (unformat (input, "rd %d", &rd_id))
280	;
281      else
282	break;
283    }
284
285  if (VNID_INVALID == vnid)
286    return clib_error_return (0, "EPG-ID must be specified");
287
288  if (add)
289    {
290      if (~0 == bd_id)
291	return clib_error_return (0, "Bridge-domain must be specified");
292      if (~0 == rd_id)
293	return clib_error_return (0, "route-domain must be specified");
294
295      gbp_endpoint_group_add_and_lock (vnid, sclass, bd_id, rd_id,
296				       uplink_sw_if_index, &retention);
297    }
298  else
299    gbp_endpoint_group_delete (vnid);
300
301  return (NULL);
302}
303
304/*?
305 * Configure a GBP Endpoint Group
306 *
307 * @cliexpar
308 * @cliexstart{gbp endpoint-group [del] epg <ID> bd <ID> rd <ID> [sclass <ID>] [<interface>]}
309 * @cliexend
310 ?*/
311/* *INDENT-OFF* */
312VLIB_CLI_COMMAND (gbp_endpoint_group_cli_node, static) = {
313  .path = "gbp endpoint-group",
314  .short_help = "gbp endpoint-group [del] epg <ID> bd <ID> rd <ID> [sclass <ID>] [<interface>]",
315  .function = gbp_endpoint_group_cli,
316};
317
318static u8 *
319format_gbp_endpoint_retention (u8 * s, va_list * args)
320{
321  gbp_endpoint_retention_t *rt = va_arg (*args, gbp_endpoint_retention_t*);
322
323  s = format (s, "[remote-EP-timeout:%d]", rt->remote_ep_timeout);
324
325  return (s);
326}
327
328u8 *
329format_gbp_endpoint_group (u8 * s, va_list * args)
330{
331  gbp_endpoint_group_t *gg = va_arg (*args, gbp_endpoint_group_t*);
332
333  if (NULL != gg)
334    s = format (s, "[%d] %d, sclass:%d bd:%d rd:%d uplink:%U retention:%U locks:%d",
335                gg - gbp_endpoint_group_pool,
336                gg->gg_vnid,
337                gg->gg_sclass,
338                gg->gg_gbd,
339                gg->gg_rd,
340                format_gbp_itf_hdl, gg->gg_uplink_itf,
341                format_gbp_endpoint_retention, &gg->gg_retention,
342                gg->gg_locks);
343  else
344    s = format (s, "NULL");
345
346  return (s);
347}
348
349static int
350gbp_endpoint_group_show_one (gbp_endpoint_group_t *gg, void *ctx)
351{
352  vlib_main_t *vm;
353
354  vm = ctx;
355  vlib_cli_output (vm, "  %U",format_gbp_endpoint_group, gg);
356
357  return (1);
358}
359
360static clib_error_t *
361gbp_endpoint_group_show (vlib_main_t * vm,
362		   unformat_input_t * input, vlib_cli_command_t * cmd)
363{
364  vlib_cli_output (vm, "Endpoint-Groups:");
365  gbp_endpoint_group_walk (gbp_endpoint_group_show_one, vm);
366
367  return (NULL);
368}
369
370
371/*?
372 * Show Group Based Policy Endpoint_Groups and derived information
373 *
374 * @cliexpar
375 * @cliexstart{show gbp endpoint_group}
376 * @cliexend
377 ?*/
378/* *INDENT-OFF* */
379VLIB_CLI_COMMAND (gbp_endpoint_group_show_node, static) = {
380  .path = "show gbp endpoint-group",
381  .short_help = "show gbp endpoint-group\n",
382  .function = gbp_endpoint_group_show,
383};
384/* *INDENT-ON* */
385
386static clib_error_t *
387gbp_endpoint_group_init (vlib_main_t * vm)
388{
389  gg_logger = vlib_log_register_class ("gbp", "epg");
390
391  return (NULL);
392}
393
394VLIB_INIT_FUNCTION (gbp_endpoint_group_init);
395
396/*
397 * fd.io coding-style-patch-verification: ON
398 *
399 * Local Variables:
400 * eval: (c-set-style "gnu")
401 * End:
402 */
403