Revert "Allow interface types to override glean adjacency behaivour"
[vpp.git] / src / vnet / adj / adj_midchain.c
1 /*
2  * Copyright (c) 2016 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 <vnet/adj/adj_nbr.h>
17 #include <vnet/adj/adj_internal.h>
18 #include <vnet/adj/adj_l2.h>
19 #include <vnet/adj/adj_nsh.h>
20 #include <vnet/adj/adj_midchain.h>
21 #include <vnet/ethernet/arp_packet.h>
22 #include <vnet/dpo/drop_dpo.h>
23 #include <vnet/fib/fib_walk.h>
24
25 /**
26  * The two midchain tx feature node indices
27  */
28 static u32 adj_midchain_tx_feature_node[VNET_LINK_NUM];
29 static u32 adj_midchain_tx_no_count_feature_node[VNET_LINK_NUM];
30
31 /**
32  * @brief Trace data for packets traversing the midchain tx node
33  */
34 typedef struct adj_midchain_tx_trace_t_
35 {
36     /**
37      * @brief the midchain adj we are traversing
38      */
39     adj_index_t ai;
40 } adj_midchain_tx_trace_t;
41
42 always_inline uword
43 adj_midchain_tx_inline (vlib_main_t * vm,
44                         vlib_node_runtime_t * node,
45                         vlib_frame_t * frame,
46                         int interface_count)
47 {
48     u32 * from, * to_next, n_left_from, n_left_to_next;
49     u32 next_index;
50     vnet_main_t *vnm = vnet_get_main ();
51     vnet_interface_main_t *im = &vnm->interface_main;
52     u32 thread_index = vm->thread_index;
53
54     /* Vector of buffer / pkt indices we're supposed to process */
55     from = vlib_frame_vector_args (frame);
56
57     /* Number of buffers / pkts */
58     n_left_from = frame->n_vectors;
59
60     /* Speculatively send the first buffer to the last disposition we used */
61     next_index = node->cached_next_index;
62
63     while (n_left_from > 0)
64     {
65         /* set up to enqueue to our disposition with index = next_index */
66         vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
67
68         while (n_left_from >= 8 && n_left_to_next > 4)
69         {
70             u32 bi0, adj_index0, next0;
71             const ip_adjacency_t * adj0;
72             const dpo_id_t *dpo0;
73             vlib_buffer_t * b0;
74             u32 bi1, adj_index1, next1;
75             const ip_adjacency_t * adj1;
76             const dpo_id_t *dpo1;
77             vlib_buffer_t * b1;
78             u32 bi2, adj_index2, next2;
79             const ip_adjacency_t * adj2;
80             const dpo_id_t *dpo2;
81             vlib_buffer_t * b2;
82             u32 bi3, adj_index3, next3;
83             const ip_adjacency_t * adj3;
84             const dpo_id_t *dpo3;
85             vlib_buffer_t * b3;
86
87             /* Prefetch next iteration. */
88             {
89                 vlib_buffer_t * p4, * p5;
90                 vlib_buffer_t * p6, * p7;
91
92                 p4 = vlib_get_buffer (vm, from[4]);
93                 p5 = vlib_get_buffer (vm, from[5]);
94                 p6 = vlib_get_buffer (vm, from[6]);
95                 p7 = vlib_get_buffer (vm, from[7]);
96
97                 vlib_prefetch_buffer_header (p4, LOAD);
98                 vlib_prefetch_buffer_header (p5, LOAD);
99                 vlib_prefetch_buffer_header (p6, LOAD);
100                 vlib_prefetch_buffer_header (p7, LOAD);
101             }
102
103             bi0 = from[0];
104             to_next[0] = bi0;
105             bi1 = from[1];
106             to_next[1] = bi1;
107             bi2 = from[2];
108             to_next[2] = bi2;
109             bi3 = from[3];
110             to_next[3] = bi3;
111
112             from += 4;
113             to_next += 4;
114             n_left_from -= 4;
115             n_left_to_next -= 4;
116
117             b0 = vlib_get_buffer(vm, bi0);
118             b1 = vlib_get_buffer(vm, bi1);
119             b2 = vlib_get_buffer(vm, bi2);
120             b3 = vlib_get_buffer(vm, bi3);
121
122             /* Follow the DPO on which the midchain is stacked */
123             adj_index0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX];
124             adj_index1 = vnet_buffer(b1)->ip.adj_index[VLIB_TX];
125             adj_index2 = vnet_buffer(b2)->ip.adj_index[VLIB_TX];
126             adj_index3 = vnet_buffer(b3)->ip.adj_index[VLIB_TX];
127
128             adj0 = adj_get(adj_index0);
129             adj1 = adj_get(adj_index1);
130             adj2 = adj_get(adj_index2);
131             adj3 = adj_get(adj_index3);
132
133             dpo0 = &adj0->sub_type.midchain.next_dpo;
134             dpo1 = &adj1->sub_type.midchain.next_dpo;
135             dpo2 = &adj2->sub_type.midchain.next_dpo;
136             dpo3 = &adj3->sub_type.midchain.next_dpo;
137
138             next0 = dpo0->dpoi_next_node;
139             next1 = dpo1->dpoi_next_node;
140             next2 = dpo2->dpoi_next_node;
141             next3 = dpo3->dpoi_next_node;
142
143             vnet_buffer(b1)->ip.adj_index[VLIB_TX] = dpo1->dpoi_index;
144             vnet_buffer(b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index;
145             vnet_buffer(b2)->ip.adj_index[VLIB_TX] = dpo2->dpoi_index;
146             vnet_buffer(b3)->ip.adj_index[VLIB_TX] = dpo3->dpoi_index;
147
148             if (interface_count)
149             {
150                 vlib_increment_combined_counter (im->combined_sw_if_counters
151                                                  + VNET_INTERFACE_COUNTER_TX,
152                                                  thread_index,
153                                                  adj0->rewrite_header.sw_if_index,
154                                                  1,
155                                                  vlib_buffer_length_in_chain (vm, b0));
156                 vlib_increment_combined_counter (im->combined_sw_if_counters
157                                                  + VNET_INTERFACE_COUNTER_TX,
158                                                  thread_index,
159                                                  adj1->rewrite_header.sw_if_index,
160                                                  1,
161                                                  vlib_buffer_length_in_chain (vm, b1));
162                 vlib_increment_combined_counter (im->combined_sw_if_counters
163                                                  + VNET_INTERFACE_COUNTER_TX,
164                                                  thread_index,
165                                                  adj2->rewrite_header.sw_if_index,
166                                                  1,
167                                                  vlib_buffer_length_in_chain (vm, b2));
168                 vlib_increment_combined_counter (im->combined_sw_if_counters
169                                                  + VNET_INTERFACE_COUNTER_TX,
170                                                  thread_index,
171                                                  adj3->rewrite_header.sw_if_index,
172                                                  1,
173                                                  vlib_buffer_length_in_chain (vm, b3));
174             }
175
176             if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
177             {
178                 adj_midchain_tx_trace_t *tr = vlib_add_trace (vm, node,
179                                                               b0, sizeof (*tr));
180                 tr->ai = adj_index0;
181             }
182             if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED))
183             {
184                 adj_midchain_tx_trace_t *tr = vlib_add_trace (vm, node,
185                                                               b1, sizeof (*tr));
186                 tr->ai = adj_index1;
187             }
188             if (PREDICT_FALSE(b2->flags & VLIB_BUFFER_IS_TRACED))
189             {
190                 adj_midchain_tx_trace_t *tr = vlib_add_trace (vm, node,
191                                                               b2, sizeof (*tr));
192                 tr->ai = adj_index2;
193             }
194             if (PREDICT_FALSE(b3->flags & VLIB_BUFFER_IS_TRACED))
195             {
196                 adj_midchain_tx_trace_t *tr = vlib_add_trace (vm, node,
197                                                               b3, sizeof (*tr));
198                 tr->ai = adj_index3;
199             }
200
201             vlib_validate_buffer_enqueue_x4 (vm, node, next_index,
202                                              to_next, n_left_to_next,
203                                              bi0, bi1, bi2, bi3,
204                                              next0, next1, next2, next3);
205         }
206         while (n_left_from > 0 && n_left_to_next > 0)
207         {
208             u32 bi0, adj_index0, next0;
209             const ip_adjacency_t * adj0;
210             const dpo_id_t *dpo0;
211             vlib_buffer_t * b0;
212
213             bi0 = from[0];
214             to_next[0] = bi0;
215             from += 1;
216             to_next += 1;
217             n_left_from -= 1;
218             n_left_to_next -= 1;
219
220             b0 = vlib_get_buffer(vm, bi0);
221
222             /* Follow the DPO on which the midchain is stacked */
223             adj_index0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX];
224             adj0 = adj_get(adj_index0);
225             dpo0 = &adj0->sub_type.midchain.next_dpo;
226             next0 = dpo0->dpoi_next_node;
227             vnet_buffer(b0)->ip.adj_index[VLIB_TX] = dpo0->dpoi_index;
228
229             if (interface_count)
230             {
231                 vlib_increment_combined_counter (im->combined_sw_if_counters
232                                                  + VNET_INTERFACE_COUNTER_TX,
233                                                  thread_index,
234                                                  adj0->rewrite_header.sw_if_index,
235                                                  1,
236                                                  vlib_buffer_length_in_chain (vm, b0));
237             }
238
239             if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
240             {
241                 adj_midchain_tx_trace_t *tr = vlib_add_trace (vm, node,
242                                                               b0, sizeof (*tr));
243                 tr->ai = adj_index0;
244             }
245
246             vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
247                                              to_next, n_left_to_next,
248                                              bi0, next0);
249         }
250
251         vlib_put_next_frame (vm, node, next_index, n_left_to_next);
252     }
253
254     return frame->n_vectors;
255 }
256
257 static u8 *
258 format_adj_midchain_tx_trace (u8 * s, va_list * args)
259 {
260     CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
261     CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
262     adj_midchain_tx_trace_t *tr = va_arg (*args, adj_midchain_tx_trace_t*);
263
264     s = format(s, "adj-midchain:[%d]:%U", tr->ai,
265                format_ip_adjacency, tr->ai,
266                FORMAT_IP_ADJACENCY_NONE);
267
268     return (s);
269 }
270
271 static uword
272 adj_midchain_tx (vlib_main_t * vm,
273                  vlib_node_runtime_t * node,
274                  vlib_frame_t * frame)
275 {
276     return (adj_midchain_tx_inline(vm, node, frame, 1));
277 }
278
279 VLIB_REGISTER_NODE (adj_midchain_tx_node, static) = {
280     .function = adj_midchain_tx,
281     .name = "adj-midchain-tx",
282     .vector_size = sizeof (u32),
283
284     .format_trace = format_adj_midchain_tx_trace,
285
286     .n_next_nodes = 1,
287     .next_nodes = {
288         [0] = "error-drop",
289     },
290 };
291
292 static uword
293 adj_midchain_tx_no_count (vlib_main_t * vm,
294                           vlib_node_runtime_t * node,
295                           vlib_frame_t * frame)
296 {
297     return (adj_midchain_tx_inline(vm, node, frame, 0));
298 }
299
300 VLIB_REGISTER_NODE (adj_midchain_tx_no_count_node, static) = {
301     .function = adj_midchain_tx_no_count,
302     .name = "adj-midchain-tx-no-count",
303     .vector_size = sizeof (u32),
304
305     .format_trace = format_adj_midchain_tx_trace,
306
307     .n_next_nodes = 1,
308     .next_nodes = {
309         [0] = "error-drop",
310     },
311 };
312
313 VNET_FEATURE_INIT (adj_midchain_tx_ip4, static) = {
314     .arc_name = "ip4-output",
315     .node_name = "adj-midchain-tx",
316     .runs_before = VNET_FEATURES ("interface-output"),
317     .feature_index_ptr = &adj_midchain_tx_feature_node[VNET_LINK_IP4],
318 };
319 VNET_FEATURE_INIT (adj_midchain_tx_no_count_ip4, static) = {
320     .arc_name = "ip4-output",
321     .node_name = "adj-midchain-tx-no-count",
322     .runs_before = VNET_FEATURES ("interface-output"),
323     .feature_index_ptr = &adj_midchain_tx_no_count_feature_node[VNET_LINK_IP4],
324 };
325 VNET_FEATURE_INIT (adj_midchain_tx_ip6, static) = {
326     .arc_name = "ip6-output",
327     .node_name = "adj-midchain-tx",
328     .runs_before = VNET_FEATURES ("interface-output"),
329     .feature_index_ptr = &adj_midchain_tx_feature_node[VNET_LINK_IP6],
330 };
331 VNET_FEATURE_INIT (adj_midchain_tx_no_count_ip6, static) = {
332     .arc_name = "ip6-output",
333     .node_name = "adj-midchain-tx-no-count",
334     .runs_before = VNET_FEATURES ("interface-output"),
335     .feature_index_ptr = &adj_midchain_tx_no_count_feature_node[VNET_LINK_IP6],
336 };
337 VNET_FEATURE_INIT (adj_midchain_tx_mpls, static) = {
338     .arc_name = "mpls-output",
339     .node_name = "adj-midchain-tx",
340     .runs_before = VNET_FEATURES ("interface-output"),
341     .feature_index_ptr = &adj_midchain_tx_feature_node[VNET_LINK_MPLS],
342 };
343 VNET_FEATURE_INIT (adj_midchain_tx_no_count_mpls, static) = {
344     .arc_name = "mpls-output",
345     .node_name = "adj-midchain-tx-no-count",
346     .runs_before = VNET_FEATURES ("interface-output"),
347     .feature_index_ptr = &adj_midchain_tx_no_count_feature_node[VNET_LINK_MPLS],
348 };
349 VNET_FEATURE_INIT (adj_midchain_tx_ethernet, static) = {
350     .arc_name = "ethernet-output",
351     .node_name = "adj-midchain-tx",
352     .runs_before = VNET_FEATURES ("error-drop"),
353     .feature_index_ptr = &adj_midchain_tx_feature_node[VNET_LINK_ETHERNET],
354 };
355 VNET_FEATURE_INIT (adj_midchain_tx_no_count_ethernet, static) = {
356     .arc_name = "ethernet-output",
357     .node_name = "adj-midchain-tx-no-count",
358     .runs_before = VNET_FEATURES ("error-drop"),
359     .feature_index_ptr = &adj_midchain_tx_no_count_feature_node[VNET_LINK_ETHERNET],
360 };
361 VNET_FEATURE_INIT (adj_midchain_tx_nsh, static) = {
362     .arc_name = "nsh-output",
363     .node_name = "adj-midchain-tx",
364     .runs_before = VNET_FEATURES ("error-drop"),
365     .feature_index_ptr = &adj_midchain_tx_feature_node[VNET_LINK_NSH],
366 };
367 VNET_FEATURE_INIT (adj_midchain_tx_no_count_nsh, static) = {
368     .arc_name = "nsh-output",
369     .node_name = "adj-midchain-tx-no-count",
370     .runs_before = VNET_FEATURES ("error-drop"),
371     .feature_index_ptr = &adj_midchain_tx_no_count_feature_node[VNET_LINK_NSH],
372 };
373
374 static inline u32
375 adj_get_midchain_node (vnet_link_t link)
376 {
377     switch (link) {
378     case VNET_LINK_IP4:
379         return (ip4_midchain_node.index);
380     case VNET_LINK_IP6:
381         return (ip6_midchain_node.index);
382     case VNET_LINK_MPLS:
383         return (mpls_midchain_node.index);
384     case VNET_LINK_ETHERNET:
385         return (adj_l2_midchain_node.index);
386     case VNET_LINK_NSH:
387         return (adj_nsh_midchain_node.index);
388     case VNET_LINK_ARP:
389         break;
390     }
391     ASSERT(0);
392     return (0);
393 }
394
395 static u8
396 adj_midchain_get_feature_arc_index_for_link_type (const ip_adjacency_t *adj)
397 {
398     u8 arc = (u8) ~0;
399     switch (adj->ia_link)
400     {
401     case VNET_LINK_IP4:
402         {
403             arc = ip4_main.lookup_main.output_feature_arc_index;
404             break;
405         }
406     case VNET_LINK_IP6:
407         {
408             arc = ip6_main.lookup_main.output_feature_arc_index;
409             break;
410         }
411     case VNET_LINK_MPLS:
412         {
413             arc = mpls_main.output_feature_arc_index;
414             break;
415         }
416     case VNET_LINK_ETHERNET:
417         {
418             arc = ethernet_main.output_feature_arc_index;
419             break;
420         }
421     case VNET_LINK_NSH:
422         {
423           arc = nsh_main_dummy.output_feature_arc_index;
424           break;
425         }
426     case VNET_LINK_ARP:
427         ASSERT(0);
428         break;
429     }
430
431     ASSERT (arc != (u8) ~0);
432
433     return (arc);
434 }
435
436 static u32
437 adj_nbr_midchain_get_tx_node (ip_adjacency_t *adj)
438 {
439     return ((adj->ia_flags & ADJ_FLAG_MIDCHAIN_NO_COUNT) ?
440             adj_midchain_tx_no_count_node.index :
441             adj_midchain_tx_node.index);
442 }
443
444 static u32
445 adj_nbr_midchain_get_feature_node (ip_adjacency_t *adj)
446 {
447     if (adj->ia_flags & ADJ_FLAG_MIDCHAIN_NO_COUNT)
448     {
449         return (adj_midchain_tx_no_count_feature_node[adj->ia_link]);
450     }
451
452     return (adj_midchain_tx_feature_node[adj->ia_link]);
453 }
454
455 /**
456  * adj_midchain_setup
457  *
458  * Setup the adj as a mid-chain
459  */
460 void
461 adj_midchain_setup (adj_index_t adj_index,
462                     adj_midchain_fixup_t fixup,
463                     const void *data,
464                     adj_flags_t flags)
465 {
466     u32 feature_index, tx_node;
467     ip_adjacency_t *adj;
468     u8 arc_index;
469
470     ASSERT(ADJ_INDEX_INVALID != adj_index);
471
472     adj = adj_get(adj_index);
473
474     adj->sub_type.midchain.fixup_func = fixup;
475     adj->sub_type.midchain.fixup_data = data;
476     adj->ia_flags |= flags;
477
478     arc_index = adj_midchain_get_feature_arc_index_for_link_type (adj);
479     feature_index = adj_nbr_midchain_get_feature_node(adj);
480     tx_node = adj_nbr_midchain_get_tx_node(adj);
481
482     vnet_feature_enable_disable_with_index (arc_index, feature_index,
483                                             adj->rewrite_header.sw_if_index,
484                                             1 /* enable */, 0, 0);
485
486     /*
487      * stack the midchain on the drop so it's ready to forward in the adj-midchain-tx.
488      * The graph arc used/created here is from the midchain-tx node to the
489      * child's registered node. This is because post adj processing the next
490      * node are any output features, then the midchain-tx.  from there we
491      * need to get to the stacked child's node.
492      */
493     dpo_stack_from_node(tx_node,
494                         &adj->sub_type.midchain.next_dpo,
495                         drop_dpo_get(vnet_link_to_dpo_proto(adj->ia_link)));
496 }
497
498 /**
499  * adj_nbr_midchain_update_rewrite
500  *
501  * Update the adjacency's rewrite string. A NULL string implies the
502  * rewrite is reset (i.e. when ARP/ND etnry is gone).
503  * NB: the adj being updated may be handling traffic in the DP.
504  */
505 void
506 adj_nbr_midchain_update_rewrite (adj_index_t adj_index,
507                                  adj_midchain_fixup_t fixup,
508                                  const void *fixup_data,
509                                  adj_flags_t flags,
510                                  u8 *rewrite)
511 {
512     ip_adjacency_t *adj;
513
514     ASSERT(ADJ_INDEX_INVALID != adj_index);
515
516     adj = adj_get(adj_index);
517
518     /*
519      * one time only update. since we don't support chainging the tunnel
520      * src,dst, this is all we need.
521      */
522     ASSERT(adj->lookup_next_index == IP_LOOKUP_NEXT_ARP);
523     /*
524      * tunnels can always provide a rewrite.
525      */
526     ASSERT(NULL != rewrite);
527
528     adj_midchain_setup(adj_index, fixup, fixup_data, flags);
529
530     /*
531      * update the rewirte with the workers paused.
532      */
533     adj_nbr_update_rewrite_internal(adj,
534                                     IP_LOOKUP_NEXT_MIDCHAIN,
535                                     adj_get_midchain_node(adj->ia_link),
536                                     adj_nbr_midchain_get_tx_node(adj),
537                                     rewrite);
538 }
539
540 /**
541  * adj_nbr_midchain_unstack
542  *
543  * Unstack the adj. stack it on drop
544  */
545 void
546 adj_nbr_midchain_unstack (adj_index_t adj_index)
547 {
548     ip_adjacency_t *adj;
549
550     ASSERT(ADJ_INDEX_INVALID != adj_index);
551
552     adj = adj_get(adj_index);
553
554     /*
555      * stack on the drop
556      */
557     dpo_stack(DPO_ADJACENCY_MIDCHAIN,
558               vnet_link_to_dpo_proto(adj->ia_link),
559               &adj->sub_type.midchain.next_dpo,
560               drop_dpo_get(vnet_link_to_dpo_proto(adj->ia_link)));
561     CLIB_MEMORY_BARRIER();
562 }
563
564 /**
565  * adj_nbr_midchain_stack
566  */
567 void
568 adj_nbr_midchain_stack (adj_index_t adj_index,
569                         const dpo_id_t *next)
570 {
571     ip_adjacency_t *adj;
572
573     ASSERT(ADJ_INDEX_INVALID != adj_index);
574
575     adj = adj_get(adj_index);
576
577     ASSERT((IP_LOOKUP_NEXT_MIDCHAIN == adj->lookup_next_index) ||
578            (IP_LOOKUP_NEXT_MCAST_MIDCHAIN == adj->lookup_next_index));
579
580     dpo_stack_from_node(adj_nbr_midchain_get_tx_node(adj),
581                         &adj->sub_type.midchain.next_dpo,
582                         next);
583 }
584
585 u8*
586 format_adj_midchain (u8* s, va_list *ap)
587 {
588     index_t index = va_arg(*ap, index_t);
589     u32 indent = va_arg(*ap, u32);
590     ip_adjacency_t * adj = adj_get(index);
591
592     s = format (s, "%U", format_vnet_link, adj->ia_link);
593     s = format (s, " via %U ",
594                 format_ip46_address, &adj->sub_type.nbr.next_hop);
595     s = format (s, " %U",
596                 format_vnet_rewrite,
597                 &adj->rewrite_header, sizeof (adj->rewrite_data), indent);
598     s = format (s, "\n%Ustacked-on:\n%U%U",
599                 format_white_space, indent,
600                 format_white_space, indent+2,
601                 format_dpo_id, &adj->sub_type.midchain.next_dpo, indent+2);
602
603     return (s);
604 }
605
606 static void
607 adj_dpo_lock (dpo_id_t *dpo)
608 {
609     adj_lock(dpo->dpoi_index);
610 }
611 static void
612 adj_dpo_unlock (dpo_id_t *dpo)
613 {
614     adj_unlock(dpo->dpoi_index);
615 }
616
617 const static dpo_vft_t adj_midchain_dpo_vft = {
618     .dv_lock = adj_dpo_lock,
619     .dv_unlock = adj_dpo_unlock,
620     .dv_format = format_adj_midchain,
621     .dv_get_urpf = adj_dpo_get_urpf,
622 };
623
624 /**
625  * @brief The per-protocol VLIB graph nodes that are assigned to a midchain
626  *        object.
627  *
628  * this means that these graph nodes are ones from which a midchain is the
629  * parent object in the DPO-graph.
630  */
631 const static char* const midchain_ip4_nodes[] =
632 {
633     "ip4-midchain",
634     NULL,
635 };
636 const static char* const midchain_ip6_nodes[] =
637 {
638     "ip6-midchain",
639     NULL,
640 };
641 const static char* const midchain_mpls_nodes[] =
642 {
643     "mpls-midchain",
644     NULL,
645 };
646 const static char* const midchain_ethernet_nodes[] =
647 {
648     "adj-l2-midchain",
649     NULL,
650 };
651 const static char* const midchain_nsh_nodes[] =
652 {
653     "adj-nsh-midchain",
654     NULL,
655 };
656
657 const static char* const * const midchain_nodes[DPO_PROTO_NUM] =
658 {
659     [DPO_PROTO_IP4]  = midchain_ip4_nodes,
660     [DPO_PROTO_IP6]  = midchain_ip6_nodes,
661     [DPO_PROTO_MPLS] = midchain_mpls_nodes,
662     [DPO_PROTO_ETHERNET] = midchain_ethernet_nodes,
663     [DPO_PROTO_NSH] = midchain_nsh_nodes,
664 };
665
666 void
667 adj_midchain_module_init (void)
668 {
669     dpo_register(DPO_ADJACENCY_MIDCHAIN, &adj_midchain_dpo_vft, midchain_nodes);
670 }