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