GBP: redirect contracts
[vpp.git] / src / plugins / gbp / gbp_contract.c
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.h>
19 #include <plugins/gbp/gbp_bridge_domain.h>
20 #include <plugins/gbp/gbp_route_domain.h>
21 #include <plugins/gbp/gbp_policy_dpo.h>
22
23 #include <vnet/dpo/load_balance.h>
24 #include <vnet/dpo/drop_dpo.h>
25
26 /**
27  * Single contract DB instance
28  */
29 gbp_contract_db_t gbp_contract_db;
30
31 gbp_contract_t *gbp_contract_pool;
32
33 vlib_log_class_t gc_logger;
34
35 fib_node_type_t gbp_next_hop_fib_type;
36
37 gbp_rule_t *gbp_rule_pool;
38 gbp_next_hop_t *gbp_next_hop_pool;
39
40 #define GBP_CONTRACT_DBG(...)                           \
41     vlib_log_notice (gc_logger, __VA_ARGS__);
42
43 index_t
44 gbp_rule_alloc (gbp_rule_action_t action,
45                 gbp_hash_mode_t hash_mode, index_t * nhs)
46 {
47   gbp_rule_t *gu;
48
49   pool_get_zero (gbp_rule_pool, gu);
50
51   gu->gu_nhs = nhs;
52   gu->gu_action = action;
53
54   return (gu - gbp_rule_pool);
55 }
56
57 index_t
58 gbp_next_hop_alloc (const ip46_address_t * ip,
59                     index_t grd, const mac_address_t * mac, index_t gbd)
60 {
61   fib_protocol_t fproto;
62   gbp_next_hop_t *gnh;
63
64   pool_get_zero (gbp_next_hop_pool, gnh);
65
66   fib_node_init (&gnh->gnh_node, gbp_next_hop_fib_type);
67
68   ip46_address_copy (&gnh->gnh_ip, ip);
69   mac_address_copy (&gnh->gnh_mac, mac);
70
71   gnh->gnh_rd = grd;
72   gnh->gnh_bd = gbd;
73
74   FOR_EACH_FIB_IP_PROTOCOL (fproto) gnh->gnh_ai[fproto] = INDEX_INVALID;
75
76   return (gnh - gbp_next_hop_pool);
77 }
78
79 static inline gbp_next_hop_t *
80 gbp_next_hop_get (index_t gui)
81 {
82   return (pool_elt_at_index (gbp_next_hop_pool, gui));
83 }
84
85 static void
86 gbp_contract_rules_free (index_t * rules)
87 {
88   index_t *gui, *gnhi;
89
90   vec_foreach (gui, rules)
91   {
92     gbp_policy_node_t pnode;
93     fib_protocol_t fproto;
94     gbp_next_hop_t *gnh;
95     gbp_rule_t *gu;
96
97     gu = gbp_rule_get (*gui);
98
99     FOR_EACH_GBP_POLICY_NODE (pnode)
100     {
101       FOR_EACH_FIB_IP_PROTOCOL (fproto)
102       {
103         dpo_reset (&gu->gu_dpo[pnode][fproto]);
104         dpo_reset (&gu->gu_dpo[pnode][fproto]);
105       }
106     }
107
108     vec_foreach (gnhi, gu->gu_nhs)
109     {
110       fib_protocol_t fproto;
111
112       gnh = gbp_next_hop_get (*gnhi);
113       gbp_bridge_domain_unlock (gnh->gnh_bd);
114       gbp_route_domain_unlock (gnh->gnh_rd);
115       gbp_endpoint_child_remove (gnh->gnh_ge, gnh->gnh_sibling);
116       gbp_endpoint_unlock (GBP_ENDPOINT_SRC_RR, gnh->gnh_ge);
117
118       FOR_EACH_FIB_IP_PROTOCOL (fproto)
119       {
120         adj_unlock (gnh->gnh_ai[fproto]);
121       }
122     }
123   }
124   vec_free (rules);
125 }
126
127 static u8 *
128 format_gbp_next_hop (u8 * s, va_list * args)
129 {
130   index_t gnhi = va_arg (*args, index_t);
131   gbp_next_hop_t *gnh;
132
133   gnh = gbp_next_hop_get (gnhi);
134
135   s = format (s, "%U, %U, %U EP:%d",
136               format_mac_address_t, &gnh->gnh_mac,
137               format_gbp_bridge_domain, gnh->gnh_bd,
138               format_ip46_address, &gnh->gnh_ip, IP46_TYPE_ANY, gnh->gnh_ge);
139
140   return (s);
141 }
142
143 static u8 *
144 format_gbp_rule_action (u8 * s, va_list * args)
145 {
146   gbp_rule_action_t action = va_arg (*args, gbp_rule_action_t);
147
148   switch (action)
149     {
150 #define _(v,a) case GBP_RULE_##v: return (format (s, "%s", a));
151       foreach_gbp_rule_action
152 #undef _
153     }
154
155   return (format (s, "unknown"));
156 }
157
158 static u8 *
159 format_gbp_hash_mode (u8 * s, va_list * args)
160 {
161   gbp_hash_mode_t action = va_arg (*args, gbp_hash_mode_t);
162
163   switch (action)
164     {
165 #define _(v,a) case GBP_HASH_MODE_##v: return (format (s, "%s", a));
166       foreach_gbp_hash_mode
167 #undef _
168     }
169
170   return (format (s, "unknown"));
171 }
172
173 static u8 *
174 format_gbp_policy_node (u8 * s, va_list * args)
175 {
176   gbp_policy_node_t action = va_arg (*args, gbp_policy_node_t);
177
178   switch (action)
179     {
180 #define _(v,a) case GBP_POLICY_NODE_##v: return (format (s, "%s", a));
181       foreach_gbp_policy_node
182 #undef _
183     }
184
185   return (format (s, "unknown"));
186 }
187
188 static u8 *
189 format_gbp_rule (u8 * s, va_list * args)
190 {
191   index_t gui = va_arg (*args, index_t);
192   gbp_policy_node_t pnode;
193   fib_protocol_t fproto;
194   gbp_rule_t *gu;
195   index_t *gnhi;
196
197   gu = gbp_rule_get (gui);
198   s = format (s, "%U", format_gbp_rule_action, gu->gu_action);
199
200   switch (gu->gu_action)
201     {
202     case GBP_RULE_PERMIT:
203     case GBP_RULE_DENY:
204       break;
205     case GBP_RULE_REDIRECT:
206       s = format (s, ", %U", format_gbp_hash_mode, gu->gu_hash_mode);
207       break;
208     }
209
210   vec_foreach (gnhi, gu->gu_nhs)
211   {
212     s = format (s, "\n      [%U]", format_gbp_next_hop, *gnhi);
213   }
214
215   FOR_EACH_GBP_POLICY_NODE (pnode)
216   {
217     s = format (s, "\n    policy-%U", format_gbp_policy_node, pnode);
218
219     FOR_EACH_FIB_IP_PROTOCOL (fproto)
220     {
221       if (dpo_id_is_valid (&gu->gu_dpo[pnode][fproto]))
222         {
223           s =
224             format (s, "\n      %U", format_dpo_id,
225                     &gu->gu_dpo[pnode][fproto], 8);
226         }
227     }
228   }
229
230   return (s);
231 }
232
233 static void
234 gbp_contract_mk_adj (gbp_next_hop_t * gnh, fib_protocol_t fproto)
235 {
236   ethernet_header_t *eth;
237   gbp_endpoint_t *ge;
238   index_t old_ai;
239   u8 *rewrite;
240
241   old_ai = gnh->gnh_ai[fproto];
242   rewrite = NULL;
243   vec_validate (rewrite, sizeof (*eth) - 1);
244   eth = (ethernet_header_t *) rewrite;
245
246   GBP_CONTRACT_DBG ("...mk-adj: %U", format_gbp_next_hop,
247                     gnh - gbp_next_hop_pool);
248
249   ge = gbp_endpoint_get (gnh->gnh_ge);
250
251   eth->type = clib_host_to_net_u16 ((fproto == FIB_PROTOCOL_IP4 ?
252                                      ETHERNET_TYPE_IP4 : ETHERNET_TYPE_IP6));
253   mac_address_to_bytes (gbp_route_domain_get_local_mac (), eth->src_address);
254   mac_address_to_bytes (&gnh->gnh_mac, eth->dst_address);
255
256   gnh->gnh_ai[fproto] =
257     adj_nbr_add_or_lock_w_rewrite (fproto,
258                                    fib_proto_to_link (fproto),
259                                    &gnh->gnh_ip, ge->ge_fwd.gef_itf, rewrite);
260
261   adj_unlock (old_ai);
262 }
263
264 static void
265 gbp_contract_mk_lb (index_t gui, fib_protocol_t fproto)
266 {
267   load_balance_path_t *paths = NULL;
268   gbp_policy_node_t pnode;
269   gbp_next_hop_t *gnh;
270   dpo_proto_t dproto;
271   gbp_rule_t *gu;
272   u32 ii;
273
274   u32 policy_nodes[] = {
275     [GBP_POLICY_NODE_L2] = gbp_policy_port_node.index,
276     [GBP_POLICY_NODE_IP4] = ip4_gbp_policy_dpo_node.index,
277     [GBP_POLICY_NODE_IP6] = ip6_gbp_policy_dpo_node.index,
278   };
279
280   GBP_CONTRACT_DBG ("..mk-lb: %U", format_gbp_rule, gui);
281
282   gu = gbp_rule_get (gui);
283   dproto = fib_proto_to_dpo (fproto);
284
285   if (GBP_RULE_REDIRECT != gu->gu_action)
286     return;
287
288   vec_foreach_index (ii, gu->gu_nhs)
289   {
290     gnh = gbp_next_hop_get (gu->gu_nhs[ii]);
291
292     gbp_contract_mk_adj (gnh, FIB_PROTOCOL_IP4);
293     gbp_contract_mk_adj (gnh, FIB_PROTOCOL_IP6);
294   }
295
296   FOR_EACH_GBP_POLICY_NODE (pnode)
297   {
298     vec_validate (paths, vec_len (gu->gu_nhs) - 1);
299
300     vec_foreach_index (ii, gu->gu_nhs)
301     {
302       gnh = gbp_next_hop_get (gu->gu_nhs[ii]);
303
304       paths[ii].path_index = FIB_NODE_INDEX_INVALID;
305       paths[ii].path_weight = 1;
306       dpo_set (&paths[ii].path_dpo, DPO_ADJACENCY,
307                dproto, gnh->gnh_ai[fproto]);
308     }
309
310     // FIXME get algo and sticky bit from contract LB algo
311     if (!dpo_id_is_valid (&gu->gu_dpo[pnode][fproto]))
312       {
313         dpo_id_t dpo = DPO_INVALID;
314
315         dpo_set (&dpo, DPO_LOAD_BALANCE, dproto,
316                  load_balance_create (vec_len (paths),
317                                       dproto, IP_FLOW_HASH_DEFAULT));
318         dpo_stack_from_node (policy_nodes[pnode],
319                              &gu->gu_dpo[pnode][fproto], &dpo);
320         dpo_reset (&dpo);
321       }
322
323     load_balance_multipath_update (&gu->gu_dpo[pnode][fproto],
324                                    paths, LOAD_BALANCE_FLAG_NONE);
325     vec_free (paths);
326   }
327 }
328
329 static void
330 gbp_contract_mk_one_lb (index_t gui)
331 {
332   gbp_contract_mk_lb (gui, FIB_PROTOCOL_IP4);
333   gbp_contract_mk_lb (gui, FIB_PROTOCOL_IP6);
334 }
335
336 static int
337 gbp_contract_next_hop_resolve (index_t gui, index_t gnhi)
338 {
339   gbp_bridge_domain_t *gbd;
340   gbp_next_hop_t *gnh;
341   ip46_address_t *ips;
342   int rv;
343
344   ips = NULL;
345   gnh = gbp_next_hop_get (gnhi);
346   gbd = gbp_bridge_domain_get (gnh->gnh_bd);
347
348   gnh->gnh_gu = gui;
349   vec_add1 (ips, gnh->gnh_ip);
350
351   /*
352    * source the endpoint this contract needs to forward via.
353    * give ofrwarding details via the spine proxy. if this EP is known
354    * to us, then since we source here with a low priority, the learned
355    * info will take precedenc.
356    */
357   rv = gbp_endpoint_update_and_lock (GBP_ENDPOINT_SRC_RR,
358                                      gbd->gb_uu_fwd_sw_if_index,
359                                      ips,
360                                      &gnh->gnh_mac,
361                                      gnh->gnh_bd, gnh->gnh_rd, EPG_INVALID,
362                                      GBP_ENDPOINT_FLAG_NONE, NULL, NULL,
363                                      &gnh->gnh_ge);
364
365   if (0 == rv)
366     {
367       gnh->gnh_sibling = gbp_endpoint_child_add (gnh->gnh_ge,
368                                                  gbp_next_hop_fib_type, gnhi);
369     }
370
371   GBP_CONTRACT_DBG ("..resolve: %d: %d: %U", gui, gnhi, format_gbp_next_hop,
372                     gnhi);
373
374   vec_free (ips);
375   return (rv);
376 }
377
378 static void
379 gbp_contract_rule_resolve (index_t gui)
380 {
381   gbp_rule_t *gu;
382   index_t *gnhi;
383
384   gu = gbp_rule_get (gui);
385
386   GBP_CONTRACT_DBG ("..resolve: %U", format_gbp_rule, gui);
387
388   vec_foreach (gnhi, gu->gu_nhs)
389   {
390     gbp_contract_next_hop_resolve (gui, *gnhi);
391   }
392 }
393
394 static void
395 gbp_contract_resolve (index_t * guis)
396 {
397   index_t *gui;
398
399   vec_foreach (gui, guis)
400   {
401     gbp_contract_rule_resolve (*gui);
402   }
403 }
404
405 static void
406 gbp_contract_mk_lbs (index_t * guis)
407 {
408   index_t *gui;
409
410   vec_foreach (gui, guis)
411   {
412     gbp_contract_mk_one_lb (*gui);
413   }
414 }
415
416 int
417 gbp_contract_update (epg_id_t src_epg,
418                      epg_id_t dst_epg, u32 acl_index, index_t * rules)
419 {
420   gbp_main_t *gm = &gbp_main;
421   u32 *acl_vec = NULL;
422   gbp_contract_t *gc;
423   index_t gci;
424   uword *p;
425
426   gbp_contract_key_t key = {
427     .gck_src = src_epg,
428     .gck_dst = dst_epg,
429   };
430
431   if (~0 == gm->gbp_acl_user_id)
432     {
433       acl_plugin_exports_init (&gm->acl_plugin);
434       gm->gbp_acl_user_id =
435         gm->acl_plugin.register_user_module ("GBP ACL", "src-epg", "dst-epg");
436     }
437
438   p = hash_get (gbp_contract_db.gc_hash, key.as_u32);
439   if (p != NULL)
440     {
441       gci = p[0];
442       gc = gbp_contract_get (gci);
443       gbp_contract_rules_free (gc->gc_rules);
444       gbp_main.acl_plugin.put_lookup_context_index (gc->gc_lc_index);
445       gc->gc_rules = NULL;
446     }
447   else
448     {
449       pool_get_zero (gbp_contract_pool, gc);
450       gc->gc_key = key;
451       gci = gc - gbp_contract_pool;
452       hash_set (gbp_contract_db.gc_hash, key.as_u32, gci);
453     }
454
455   GBP_CONTRACT_DBG ("update: %U", format_gbp_contract, gci);
456
457   gc->gc_rules = rules;
458   gbp_contract_resolve (gc->gc_rules);
459   gbp_contract_mk_lbs (gc->gc_rules);
460
461   gc->gc_acl_index = acl_index;
462   gc->gc_lc_index =
463     gm->acl_plugin.get_lookup_context_index (gm->gbp_acl_user_id,
464                                              src_epg, dst_epg);
465
466   vec_add1 (acl_vec, gc->gc_acl_index);
467   gm->acl_plugin.set_acl_vec_for_context (gc->gc_lc_index, acl_vec);
468   vec_free (acl_vec);
469
470   return (0);
471 }
472
473 int
474 gbp_contract_delete (epg_id_t src_epg, epg_id_t dst_epg)
475 {
476   gbp_contract_key_t key = {
477     .gck_src = src_epg,
478     .gck_dst = dst_epg,
479   };
480   gbp_contract_t *gc;
481   uword *p;
482
483   p = hash_get (gbp_contract_db.gc_hash, key.as_u32);
484   if (p != NULL)
485     {
486       gc = gbp_contract_get (p[0]);
487
488       gbp_contract_rules_free (gc->gc_rules);
489       gbp_main.acl_plugin.put_lookup_context_index (gc->gc_lc_index);
490
491       hash_unset (gbp_contract_db.gc_hash, key.as_u32);
492       pool_put (gbp_contract_pool, gc);
493
494       return (0);
495     }
496
497   return (VNET_API_ERROR_NO_SUCH_ENTRY);
498 }
499
500 void
501 gbp_contract_walk (gbp_contract_cb_t cb, void *ctx)
502 {
503   gbp_contract_t *gc;
504
505   /* *INDENT-OFF* */
506   pool_foreach(gc, gbp_contract_pool,
507   ({
508     if (!cb(gc, ctx))
509       break;
510   }));
511   /* *INDENT-ON* */
512 }
513
514 static clib_error_t *
515 gbp_contract_cli (vlib_main_t * vm,
516                   unformat_input_t * input, vlib_cli_command_t * cmd)
517 {
518   epg_id_t src_epg_id = EPG_INVALID, dst_epg_id = EPG_INVALID;
519   u32 acl_index = ~0;
520   u8 add = 1;
521
522   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
523     {
524       if (unformat (input, "add"))
525         add = 1;
526       else if (unformat (input, "del"))
527         add = 0;
528       else if (unformat (input, "src-epg %d", &src_epg_id))
529         ;
530       else if (unformat (input, "dst-epg %d", &dst_epg_id))
531         ;
532       else if (unformat (input, "acl-index %d", &acl_index))
533         ;
534       else
535         break;
536     }
537
538   if (EPG_INVALID == src_epg_id)
539     return clib_error_return (0, "Source EPG-ID must be specified");
540   if (EPG_INVALID == dst_epg_id)
541     return clib_error_return (0, "Destination EPG-ID must be specified");
542
543   if (add)
544     {
545       gbp_contract_update (src_epg_id, dst_epg_id, acl_index, NULL);
546     }
547   else
548     {
549       gbp_contract_delete (src_epg_id, dst_epg_id);
550     }
551
552   return (NULL);
553 }
554
555 /*?
556  * Configure a GBP Contract
557  *
558  * @cliexpar
559  * @cliexstart{set gbp contract [del] src-epg <ID> dst-epg <ID> acl-index <ACL>}
560  * @cliexend
561  ?*/
562 /* *INDENT-OFF* */
563 VLIB_CLI_COMMAND (gbp_contract_cli_node, static) =
564 {
565   .path = "gbp contract",
566   .short_help =
567     "gbp contract [del] src-epg <ID> dst-epg <ID> acl-index <ACL>",
568   .function = gbp_contract_cli,
569 };
570 /* *INDENT-ON* */
571
572 static u8 *
573 format_gbp_contract_key (u8 * s, va_list * args)
574 {
575   gbp_contract_key_t *gck = va_arg (*args, gbp_contract_key_t *);
576
577   s = format (s, "{%d,%d}", gck->gck_src, gck->gck_dst);
578
579   return (s);
580 }
581
582 u8 *
583 format_gbp_contract (u8 * s, va_list * args)
584 {
585   index_t gci = va_arg (*args, index_t);
586   gbp_contract_t *gc;
587   index_t *gui;
588
589   gc = gbp_contract_get (gci);
590
591   s = format (s, "%U: acl-index:%d",
592               format_gbp_contract_key, &gc->gc_key, gc->gc_acl_index);
593
594   vec_foreach (gui, gc->gc_rules)
595   {
596     s = format (s, "\n    %d: %U", *gui, format_gbp_rule, *gui);
597   }
598
599   return (s);
600 }
601
602 static clib_error_t *
603 gbp_contract_show (vlib_main_t * vm,
604                    unformat_input_t * input, vlib_cli_command_t * cmd)
605 {
606   index_t gci;
607
608   vlib_cli_output (vm, "Contracts:");
609
610   /* *INDENT-OFF* */
611   pool_foreach_index (gci, gbp_contract_pool,
612   ({
613     vlib_cli_output (vm, "  [%d] %U", gci, format_gbp_contract, gci);
614   }));
615   /* *INDENT-ON* */
616
617   return (NULL);
618 }
619
620 /*?
621  * Show Group Based Policy Contracts
622  *
623  * @cliexpar
624  * @cliexstart{show gbp contract}
625  * @cliexend
626  ?*/
627 /* *INDENT-OFF* */
628 VLIB_CLI_COMMAND (gbp_contract_show_node, static) = {
629   .path = "show gbp contract",
630   .short_help = "show gbp contract\n",
631   .function = gbp_contract_show,
632 };
633 /* *INDENT-ON* */
634
635 static fib_node_t *
636 gbp_next_hop_get_node (fib_node_index_t index)
637 {
638   gbp_next_hop_t *gnh;
639
640   gnh = gbp_next_hop_get (index);
641
642   return (&gnh->gnh_node);
643 }
644
645 static void
646 gbp_next_hop_last_lock_gone (fib_node_t * node)
647 {
648   ASSERT (0);
649 }
650
651 static gbp_next_hop_t *
652 gbp_next_hop_from_fib_node (fib_node_t * node)
653 {
654   ASSERT (gbp_next_hop_fib_type == node->fn_type);
655   return ((gbp_next_hop_t *) node);
656 }
657
658 static fib_node_back_walk_rc_t
659 gbp_next_hop_back_walk_notify (fib_node_t * node,
660                                fib_node_back_walk_ctx_t * ctx)
661 {
662   gbp_next_hop_t *gnh;
663
664   gnh = gbp_next_hop_from_fib_node (node);
665
666   gbp_contract_mk_one_lb (gnh->gnh_gu);
667
668   return (FIB_NODE_BACK_WALK_CONTINUE);
669 }
670
671 /*
672  * The FIB path's graph node virtual function table
673  */
674 static const fib_node_vft_t gbp_next_hop_vft = {
675   .fnv_get = gbp_next_hop_get_node,
676   .fnv_last_lock = gbp_next_hop_last_lock_gone,
677   .fnv_back_walk = gbp_next_hop_back_walk_notify,
678   // .fnv_mem_show = fib_path_memory_show,
679 };
680
681 static clib_error_t *
682 gbp_contract_init (vlib_main_t * vm)
683 {
684   gc_logger = vlib_log_register_class ("gbp", "con");
685   gbp_next_hop_fib_type = fib_node_register_new_type (&gbp_next_hop_vft);
686
687   return (NULL);
688 }
689
690 VLIB_INIT_FUNCTION (gbp_contract_init);
691
692 /*
693  * fd.io coding-style-patch-verification: ON
694  *
695  * Local Variables:
696  * eval: (c-set-style "gnu")
697  * End:
698  */