GBP: learn from ARP and L2 packets
[vpp.git] / src / plugins / gbp / gbp_learn_node.c
1 /*
2  * Copyright (c) 2018 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <plugins/gbp/gbp.h>
17 #include <plugins/gbp/gbp_learn.h>
18 #include <plugins/gbp/gbp_bridge_domain.h>
19 #include <vlibmemory/api.h>
20
21 #include <vnet/util/throttle.h>
22 #include <vnet/l2/l2_input.h>
23 #include <vnet/fib/fib_table.h>
24 #include <vnet/vxlan-gbp/vxlan_gbp_packet.h>
25 #include <vnet/ethernet/arp_packet.h>
26
27 #define GBP_LEARN_DBG(...)                                      \
28     vlib_log_debug (gbp_learn_main.gl_logger, __VA_ARGS__);
29
30 #define foreach_gbp_learn                      \
31   _(DROP,    "drop")
32
33 typedef enum
34 {
35 #define _(sym,str) GBP_LEARN_ERROR_##sym,
36   foreach_gbp_learn
37 #undef _
38     GBP_LEARN_N_ERROR,
39 } gbp_learn_error_t;
40
41 static char *gbp_learn_error_strings[] = {
42 #define _(sym,string) string,
43   foreach_gbp_learn
44 #undef _
45 };
46
47 typedef enum
48 {
49 #define _(sym,str) GBP_LEARN_NEXT_##sym,
50   foreach_gbp_learn
51 #undef _
52     GBP_LEARN_N_NEXT,
53 } gbp_learn_next_t;
54
55 typedef struct gbp_learn_l2_t_
56 {
57   ip46_address_t ip;
58   mac_address_t mac;
59   u32 sw_if_index;
60   u32 bd_index;
61   epg_id_t epg;
62   ip46_address_t outer_src;
63   ip46_address_t outer_dst;
64 } gbp_learn_l2_t;
65
66
67 static void
68 gbp_learn_l2_cp (const gbp_learn_l2_t * gl2)
69 {
70   ip46_address_t *ips = NULL;
71
72   GBP_LEARN_DBG ("L2 EP: %U %U, %d",
73                  format_mac_address_t, &gl2->mac,
74                  format_ip46_address, &gl2->ip, IP46_TYPE_ANY, gl2->epg);
75
76   if (!ip46_address_is_zero (&gl2->ip))
77     vec_add1 (ips, gl2->ip);
78
79   /*
80    * flip the source and dst, since that's how it was received, this API
81    * takes how it's sent
82    */
83   gbp_endpoint_update_and_lock (GBP_ENDPOINT_SRC_DP,
84                                 gl2->sw_if_index, ips,
85                                 &gl2->mac, INDEX_INVALID,
86                                 INDEX_INVALID, gl2->epg,
87                                 (GBP_ENDPOINT_FLAG_LEARNT |
88                                  GBP_ENDPOINT_FLAG_REMOTE),
89                                 &gl2->outer_dst, &gl2->outer_src, NULL);
90   vec_free (ips);
91 }
92
93 static void
94 gbp_learn_l2_ip4_dp (const u8 * mac, const ip4_address_t * ip,
95                      u32 bd_index, u32 sw_if_index, epg_id_t epg,
96                      const ip4_address_t * outer_src,
97                      const ip4_address_t * outer_dst)
98 {
99   gbp_learn_l2_t gl2 = {
100     .sw_if_index = sw_if_index,
101     .bd_index = bd_index,
102     .epg = epg,
103     .ip.ip4 = *ip,
104     .outer_src.ip4 = *outer_src,
105     .outer_dst.ip4 = *outer_dst,
106   };
107   mac_address_from_bytes (&gl2.mac, mac);
108
109   vl_api_rpc_call_main_thread (gbp_learn_l2_cp, (u8 *) & gl2, sizeof (gl2));
110 }
111
112 static void
113 gbp_learn_l2_ip6_dp (const u8 * mac, const ip6_address_t * ip,
114                      u32 bd_index, u32 sw_if_index, epg_id_t epg,
115                      const ip4_address_t * outer_src,
116                      const ip4_address_t * outer_dst)
117 {
118   gbp_learn_l2_t gl2 = {
119     .sw_if_index = sw_if_index,
120     .bd_index = bd_index,
121     .epg = epg,
122     .ip.ip6 = *ip,
123     .outer_src.ip4 = *outer_src,
124     .outer_dst.ip4 = *outer_dst,
125   };
126   mac_address_from_bytes (&gl2.mac, mac);
127
128   vl_api_rpc_call_main_thread (gbp_learn_l2_cp, (u8 *) & gl2, sizeof (gl2));
129 }
130
131 static void
132 gbp_learn_l2_dp (const u8 * mac, u32 bd_index, u32 sw_if_index,
133                  epg_id_t epg,
134                  const ip4_address_t * outer_src,
135                  const ip4_address_t * outer_dst)
136 {
137   gbp_learn_l2_t gl2 = {
138     .sw_if_index = sw_if_index,
139     .bd_index = bd_index,
140     .epg = epg,
141     .outer_src.ip4 = *outer_src,
142     .outer_dst.ip4 = *outer_dst,
143   };
144   mac_address_from_bytes (&gl2.mac, mac);
145
146   vl_api_rpc_call_main_thread (gbp_learn_l2_cp, (u8 *) & gl2, sizeof (gl2));
147 }
148
149 /**
150  * per-packet trace data
151  */
152 typedef struct gbp_learn_l2_trace_t_
153 {
154   /* per-pkt trace data */
155   mac_address_t mac;
156   u32 sw_if_index;
157   u32 new;
158   u32 throttled;
159   u32 epg;
160   u32 d_bit;
161   gbp_bridge_domain_flags_t gb_flags;
162 } gbp_learn_l2_trace_t;
163
164 always_inline void
165 gbp_learn_get_outer (const ethernet_header_t * eh0,
166                      ip4_address_t * outer_src, ip4_address_t * outer_dst)
167 {
168   ip4_header_t *ip0;
169   u8 *buff;
170
171   /* rewind back to the ivxlan header */
172   buff = (u8 *) eh0;
173   buff -= (sizeof (vxlan_gbp_header_t) +
174            sizeof (udp_header_t) + sizeof (ip4_header_t));
175
176   ip0 = (ip4_header_t *) buff;
177
178   *outer_src = ip0->src_address;
179   *outer_dst = ip0->dst_address;
180 }
181
182 VLIB_NODE_FN (gbp_learn_l2_node) (vlib_main_t * vm,
183                                   vlib_node_runtime_t * node,
184                                   vlib_frame_t * frame)
185 {
186   u32 n_left_from, *from, *to_next, next_index, thread_index, seed;
187   gbp_learn_main_t *glm;
188   f64 time_now;
189
190   glm = &gbp_learn_main;
191   next_index = 0;
192   n_left_from = frame->n_vectors;
193   from = vlib_frame_vector_args (frame);
194   time_now = vlib_time_now (vm);
195   thread_index = vm->thread_index;
196
197   seed = throttle_seed (&glm->gl_l2_throttle, thread_index, time_now);
198
199   while (n_left_from > 0)
200     {
201       u32 n_left_to_next;
202
203       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
204
205       while (n_left_from > 0 && n_left_to_next > 0)
206         {
207           ip4_address_t outer_src, outer_dst;
208           u32 bi0, sw_if_index0, t0, epg0;
209           const ethernet_header_t *eh0;
210           gbp_bridge_domain_t *gb0;
211           gbp_learn_next_t next0;
212           gbp_endpoint_t *ge0;
213           vlib_buffer_t *b0;
214
215           next0 = GBP_LEARN_NEXT_DROP;
216           bi0 = from[0];
217           to_next[0] = bi0;
218           from += 1;
219           to_next += 1;
220           n_left_from -= 1;
221           n_left_to_next -= 1;
222
223           b0 = vlib_get_buffer (vm, bi0);
224           sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
225
226           eh0 = vlib_buffer_get_current (b0);
227           epg0 = vnet_buffer2 (b0)->gbp.src_epg;
228
229           next0 = vnet_l2_feature_next (b0, glm->gl_l2_input_feat_next,
230                                         L2INPUT_FEAT_GBP_LEARN);
231
232           ge0 = gbp_endpoint_find_mac (eh0->src_address,
233                                        vnet_buffer (b0)->l2.bd_index);
234           gb0 =
235             gbp_bridge_domain_get_by_bd_index (vnet_buffer (b0)->l2.bd_index);
236
237           if ((vnet_buffer2 (b0)->gbp.flags & VXLAN_GBP_GPFLAGS_D) ||
238               (gb0->gb_flags & GBP_BD_FLAG_DO_NOT_LEARN))
239             {
240               t0 = 1;
241               goto trace;
242             }
243
244           /*
245            * check for new EP or a moved EP
246            */
247           if (NULL == ge0 || ge0->ge_fwd.gef_itf != sw_if_index0)
248
249             {
250               /*
251                * use the last 4 bytes of the mac address as the hash for the EP
252                */
253               t0 = throttle_check (&glm->gl_l2_throttle, thread_index,
254                                    *((u32 *) (eh0->src_address + 2)), seed);
255               if (!t0)
256                 {
257                   gbp_learn_get_outer (eh0, &outer_src, &outer_dst);
258
259                   if (outer_src.as_u32 == 0 || outer_dst.as_u32 == 0)
260                     {
261                       t0 = 2;
262                       goto trace;
263                     }
264
265                   switch (clib_net_to_host_u16 (eh0->type))
266                     {
267                     case ETHERNET_TYPE_IP4:
268                       {
269                         const ip4_header_t *ip0;
270
271                         ip0 = (ip4_header_t *) (eh0 + 1);
272
273                         gbp_learn_l2_ip4_dp (eh0->src_address,
274                                              &ip0->src_address,
275                                              vnet_buffer (b0)->l2.bd_index,
276                                              sw_if_index0, epg0,
277                                              &outer_src, &outer_dst);
278
279                         break;
280                       }
281                     case ETHERNET_TYPE_IP6:
282                       {
283                         const ip6_header_t *ip0;
284
285                         ip0 = (ip6_header_t *) (eh0 + 1);
286
287                         gbp_learn_l2_ip6_dp (eh0->src_address,
288                                              &ip0->src_address,
289                                              vnet_buffer (b0)->l2.bd_index,
290                                              sw_if_index0, epg0,
291                                              &outer_src, &outer_dst);
292
293                         break;
294                       }
295                     case ETHERNET_TYPE_ARP:
296                       {
297                         const ethernet_arp_header_t *arp0;
298
299                         arp0 = (ethernet_arp_header_t *) (eh0 + 1);
300
301                         gbp_learn_l2_ip4_dp (eh0->src_address,
302                                              &arp0->ip4_over_ethernet[0].ip4,
303                                              vnet_buffer (b0)->l2.bd_index,
304                                              sw_if_index0, epg0,
305                                              &outer_src, &outer_dst);
306                         break;
307                       }
308                     default:
309                       gbp_learn_l2_dp (eh0->src_address,
310                                        vnet_buffer (b0)->l2.bd_index,
311                                        sw_if_index0, epg0,
312                                        &outer_src, &outer_dst);
313                       break;
314                     }
315                 }
316             }
317           else
318             {
319               /*
320                * this update could happen simultaneoulsy from multiple workers
321                * but that's ok we are not interested in being very accurate.
322                */
323               t0 = 0;
324               ge0->ge_last_time = time_now;
325             }
326         trace:
327           if (PREDICT_FALSE ((b0->flags & VLIB_BUFFER_IS_TRACED)))
328             {
329               gbp_learn_l2_trace_t *t =
330                 vlib_add_trace (vm, node, b0, sizeof (*t));
331               clib_memcpy_fast (t->mac.bytes, eh0->src_address, 6);
332               t->new = (NULL == ge0);
333               t->throttled = t0;
334               t->sw_if_index = sw_if_index0;
335               t->epg = epg0;
336               t->gb_flags = gb0->gb_flags;
337               t->d_bit = ! !(vnet_buffer2 (b0)->gbp.flags &
338                              VXLAN_GBP_GPFLAGS_D);
339             }
340
341           /* verify speculative enqueue, maybe switch current next frame */
342           vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
343                                            to_next, n_left_to_next,
344                                            bi0, next0);
345         }
346
347       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
348     }
349
350   return frame->n_vectors;
351 }
352
353 /* packet trace format function */
354 static u8 *
355 format_gbp_learn_l2_trace (u8 * s, va_list * args)
356 {
357   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
358   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
359   gbp_learn_l2_trace_t *t = va_arg (*args, gbp_learn_l2_trace_t *);
360
361   s = format (s, "new:%d throttled:%d d-bit:%d mac:%U itf:%d epg:%d"
362               " gb-flags:%U",
363               t->new, t->throttled, t->d_bit,
364               format_mac_address_t, &t->mac, t->sw_if_index, t->epg,
365               format_gbp_bridge_domain_flags, t->gb_flags);
366
367   return s;
368 }
369
370 /* *INDENT-OFF* */
371 VLIB_REGISTER_NODE (gbp_learn_l2_node) = {
372   .name = "gbp-learn-l2",
373   .vector_size = sizeof (u32),
374   .format_trace = format_gbp_learn_l2_trace,
375   .type = VLIB_NODE_TYPE_INTERNAL,
376
377   .n_errors = ARRAY_LEN(gbp_learn_error_strings),
378   .error_strings = gbp_learn_error_strings,
379
380   .n_next_nodes = GBP_LEARN_N_NEXT,
381
382   .next_nodes = {
383     [GBP_LEARN_NEXT_DROP] = "error-drop",
384   },
385 };
386 /* *INDENT-ON* */
387
388 typedef struct gbp_learn_l3_t_
389 {
390   ip46_address_t ip;
391   u32 fib_index;
392   u32 sw_if_index;
393   epg_id_t epg;
394   ip46_address_t outer_src;
395   ip46_address_t outer_dst;
396 } gbp_learn_l3_t;
397
398 static void
399 gbp_learn_l3_cp (const gbp_learn_l3_t * gl3)
400 {
401   ip46_address_t *ips = NULL;
402
403   GBP_LEARN_DBG ("L3 EP: %U, %d", format_ip46_address, &gl3->ip,
404                  IP46_TYPE_ANY, gl3->epg);
405
406   vec_add1 (ips, gl3->ip);
407
408   gbp_endpoint_update_and_lock (GBP_ENDPOINT_SRC_DP,
409                                 gl3->sw_if_index, ips, NULL,
410                                 INDEX_INVALID, INDEX_INVALID, gl3->epg,
411                                 (GBP_ENDPOINT_FLAG_REMOTE |
412                                  GBP_ENDPOINT_FLAG_LEARNT),
413                                 &gl3->outer_dst, &gl3->outer_src, NULL);
414   vec_free (ips);
415 }
416
417 static void
418 gbp_learn_ip4_dp (const ip4_address_t * ip,
419                   u32 fib_index, u32 sw_if_index, epg_id_t epg,
420                   const ip4_address_t * outer_src,
421                   const ip4_address_t * outer_dst)
422 {
423   /* *INDENT-OFF* */
424   gbp_learn_l3_t gl3 = {
425     .ip = {
426       .ip4 = *ip,
427     },
428     .sw_if_index = sw_if_index,
429     .fib_index = fib_index,
430     .epg = epg,
431     .outer_src.ip4 = *outer_src,
432     .outer_dst.ip4 = *outer_dst,
433   };
434   /* *INDENT-ON* */
435
436   vl_api_rpc_call_main_thread (gbp_learn_l3_cp, (u8 *) & gl3, sizeof (gl3));
437 }
438
439 static void
440 gbp_learn_ip6_dp (const ip6_address_t * ip,
441                   u32 fib_index, u32 sw_if_index, epg_id_t epg,
442                   const ip4_address_t * outer_src,
443                   const ip4_address_t * outer_dst)
444 {
445   /* *INDENT-OFF* */
446   gbp_learn_l3_t gl3 = {
447     .ip = {
448       .ip6 = *ip,
449     },
450     .sw_if_index = sw_if_index,
451     .fib_index = fib_index,
452     .epg = epg,
453     .outer_src.ip4 = *outer_src,
454     .outer_dst.ip4 = *outer_dst,
455   };
456   /* *INDENT-ON* */
457
458   vl_api_rpc_call_main_thread (gbp_learn_l3_cp, (u8 *) & gl3, sizeof (gl3));
459 }
460
461 /**
462  * per-packet trace data
463  */
464 typedef struct gbp_learn_l3_trace_t_
465 {
466   /* per-pkt trace data */
467   ip46_address_t ip;
468   u32 sw_if_index;
469   u32 new;
470   u32 throttled;
471   u32 epg;
472 } gbp_learn_l3_trace_t;
473
474 static uword
475 gbp_learn_l3 (vlib_main_t * vm,
476               vlib_node_runtime_t * node, vlib_frame_t * frame,
477               fib_protocol_t fproto)
478 {
479   u32 n_left_from, *from, *to_next, next_index, thread_index, seed;
480   gbp_learn_main_t *glm;
481   f64 time_now;
482
483   glm = &gbp_learn_main;
484   next_index = 0;
485   n_left_from = frame->n_vectors;
486   from = vlib_frame_vector_args (frame);
487   time_now = vlib_time_now (vm);
488   thread_index = vm->thread_index;
489
490   seed = throttle_seed (&glm->gl_l3_throttle, thread_index, time_now);
491
492   while (n_left_from > 0)
493     {
494       u32 n_left_to_next;
495
496       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
497
498       while (n_left_from > 0 && n_left_to_next > 0)
499         {
500           u32 bi0, sw_if_index0, t0, epg0, fib_index0;
501           CLIB_UNUSED (const ip4_header_t *) ip4_0;
502           CLIB_UNUSED (const ip6_header_t *) ip6_0;
503           ip4_address_t outer_src, outer_dst;
504           ethernet_header_t *eth0;
505           gbp_learn_next_t next0;
506           gbp_endpoint_t *ge0;
507           vlib_buffer_t *b0;
508
509           next0 = GBP_LEARN_NEXT_DROP;
510           bi0 = from[0];
511           to_next[0] = bi0;
512           from += 1;
513           to_next += 1;
514           n_left_from -= 1;
515           n_left_to_next -= 1;
516
517           b0 = vlib_get_buffer (vm, bi0);
518           sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
519           epg0 = vnet_buffer2 (b0)->gbp.src_epg;
520           ip6_0 = NULL;
521           ip4_0 = NULL;
522
523           vnet_feature_next (&next0, b0);
524
525           if (vnet_buffer2 (b0)->gbp.flags & VXLAN_GBP_GPFLAGS_D)
526             {
527               t0 = 1;
528               ge0 = NULL;
529               goto trace;
530             }
531
532           fib_index0 = fib_table_get_index_for_sw_if_index (fproto,
533                                                             sw_if_index0);
534
535           if (FIB_PROTOCOL_IP6 == fproto)
536             {
537               ip6_0 = vlib_buffer_get_current (b0);
538               eth0 = (ethernet_header_t *) (((u8 *) ip6_0) - sizeof (*eth0));
539
540               gbp_learn_get_outer (eth0, &outer_src, &outer_dst);
541
542               ge0 = gbp_endpoint_find_ip6 (&ip6_0->src_address, fib_index0);
543
544               if (NULL == ge0)
545                 {
546                   t0 = throttle_check (&glm->gl_l3_throttle,
547                                        thread_index,
548                                        ip6_address_hash_to_u32
549                                        (&ip6_0->src_address), seed);
550
551                   if (!t0)
552                     {
553                       gbp_learn_ip6_dp (&ip6_0->src_address,
554                                         fib_index0, sw_if_index0, epg0,
555                                         &outer_src, &outer_dst);
556                     }
557                 }
558               else
559                 {
560                   /*
561                    * this update could happen simultaneoulsy from multiple
562                    * workers but that's ok we are not interested in being
563                    * very accurate.
564                    */
565                   t0 = 0;
566                   ge0->ge_last_time = time_now;
567                 }
568             }
569           else
570             {
571               ip4_0 = vlib_buffer_get_current (b0);
572               eth0 = (ethernet_header_t *) (((u8 *) ip4_0) - sizeof (*eth0));
573
574               gbp_learn_get_outer (eth0, &outer_src, &outer_dst);
575               ge0 = gbp_endpoint_find_ip4 (&ip4_0->src_address, fib_index0);
576
577               if (NULL == ge0)
578                 {
579                   t0 = throttle_check (&glm->gl_l3_throttle, thread_index,
580                                        ip4_0->src_address.as_u32, seed);
581
582                   if (!t0)
583                     {
584                       gbp_learn_ip4_dp (&ip4_0->src_address,
585                                         fib_index0, sw_if_index0, epg0,
586                                         &outer_src, &outer_dst);
587                     }
588                 }
589               else
590                 {
591                   /*
592                    * this update could happen simultaneoulsy from multiple
593                    * workers but that's ok we are not interested in being
594                    * very accurate.
595                    */
596                   t0 = 0;
597                   ge0->ge_last_time = time_now;
598                 }
599             }
600         trace:
601           if (PREDICT_FALSE ((b0->flags & VLIB_BUFFER_IS_TRACED)))
602             {
603               gbp_learn_l3_trace_t *t;
604
605               t = vlib_add_trace (vm, node, b0, sizeof (*t));
606               if (FIB_PROTOCOL_IP6 == fproto && ip6_0)
607                 ip46_address_set_ip6 (&t->ip, &ip6_0->src_address);
608               if (FIB_PROTOCOL_IP4 == fproto && ip4_0)
609                 ip46_address_set_ip4 (&t->ip, &ip4_0->src_address);
610               t->new = (NULL == ge0);
611               t->throttled = t0;
612               t->sw_if_index = sw_if_index0;
613               t->epg = epg0;
614             }
615
616           vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
617                                            to_next, n_left_to_next,
618                                            bi0, next0);
619         }
620
621       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
622     }
623
624   return frame->n_vectors;
625 }
626
627 /* packet trace format function */
628 static u8 *
629 format_gbp_learn_l3_trace (u8 * s, va_list * args)
630 {
631   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
632   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
633   gbp_learn_l3_trace_t *t = va_arg (*args, gbp_learn_l3_trace_t *);
634
635   s = format (s, "new:%d throttled:%d ip:%U itf:%d epg:%d",
636               t->new, t->throttled,
637               format_ip46_address, &t->ip, IP46_TYPE_ANY, t->sw_if_index,
638               t->epg);
639
640   return s;
641 }
642
643 VLIB_NODE_FN (gbp_learn_ip4_node) (vlib_main_t * vm,
644                                    vlib_node_runtime_t * node,
645                                    vlib_frame_t * frame)
646 {
647   return (gbp_learn_l3 (vm, node, frame, FIB_PROTOCOL_IP4));
648 }
649
650 VLIB_NODE_FN (gbp_learn_ip6_node) (vlib_main_t * vm,
651                                    vlib_node_runtime_t * node,
652                                    vlib_frame_t * frame)
653 {
654   return (gbp_learn_l3 (vm, node, frame, FIB_PROTOCOL_IP6));
655 }
656
657 /* *INDENT-OFF* */
658 VLIB_REGISTER_NODE (gbp_learn_ip4_node) = {
659   .name = "gbp-learn-ip4",
660   .vector_size = sizeof (u32),
661   .format_trace = format_gbp_learn_l3_trace,
662   .type = VLIB_NODE_TYPE_INTERNAL,
663 };
664
665 VNET_FEATURE_INIT (gbp_learn_ip4, static) =
666 {
667   .arc_name = "ip4-unicast",
668   .node_name = "gbp-learn-ip4",
669 };
670
671 VLIB_REGISTER_NODE (gbp_learn_ip6_node) = {
672   .name = "gbp-learn-ip6",
673   .vector_size = sizeof (u32),
674   .format_trace = format_gbp_learn_l3_trace,
675   .type = VLIB_NODE_TYPE_INTERNAL,
676 };
677
678 VNET_FEATURE_INIT (gbp_learn_ip6, static) =
679 {
680   .arc_name = "ip6-unicast",
681   .node_name = "gbp-learn-ip6",
682 };
683
684 /* *INDENT-ON* */
685
686 /*
687  * fd.io coding-style-patch-verification: ON
688  *
689  * Local Variables:
690  * eval: (c-set-style "gnu")
691  * End:
692  */