Line data Source code
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/dpo/dvr_dpo.h>
17 : #include <vnet/fib/fib_node.h>
18 : #include <vnet/ip/ip.h>
19 : #include <vnet/ethernet/ethernet.h>
20 : #include <vnet/l2/l2_input.h>
21 :
22 : #ifndef CLIB_MARCH_VARIANT
23 : dvr_dpo_t *dvr_dpo_pool;
24 :
25 : /**
26 : * The 'DB' of DVR DPOs.
27 : * There is one per-interface per-L3 proto, so this is a per-interface vector
28 : */
29 : static index_t *dvr_dpo_db[DPO_PROTO_NUM];
30 :
31 : static dvr_dpo_t *
32 7 : dvr_dpo_alloc (void)
33 : {
34 : dvr_dpo_t *dd;
35 :
36 7 : pool_get(dvr_dpo_pool, dd);
37 :
38 7 : return (dd);
39 : }
40 :
41 : static inline dvr_dpo_t *
42 82 : dvr_dpo_get_from_dpo (const dpo_id_t *dpo)
43 : {
44 82 : ASSERT(DPO_DVR == dpo->dpoi_type);
45 :
46 82 : return (dvr_dpo_get(dpo->dpoi_index));
47 : }
48 :
49 : static inline index_t
50 15 : dvr_dpo_get_index (dvr_dpo_t *dd)
51 : {
52 15 : return (dd - dvr_dpo_pool);
53 : }
54 :
55 : static void
56 41 : dvr_dpo_lock (dpo_id_t *dpo)
57 : {
58 : dvr_dpo_t *dd;
59 :
60 41 : dd = dvr_dpo_get_from_dpo(dpo);
61 41 : dd->dd_locks++;
62 41 : }
63 :
64 : static void
65 41 : dvr_dpo_unlock (dpo_id_t *dpo)
66 : {
67 : dvr_dpo_t *dd;
68 :
69 41 : dd = dvr_dpo_get_from_dpo(dpo);
70 41 : dd->dd_locks--;
71 :
72 41 : if (0 == dd->dd_locks)
73 : {
74 7 : if (DPO_PROTO_IP4 == dd->dd_proto)
75 : {
76 5 : vnet_feature_enable_disable ("ip4-output", "ip4-dvr-reinject",
77 : dd->dd_sw_if_index, 0, 0, 0);
78 : }
79 : else
80 : {
81 2 : vnet_feature_enable_disable ("ip6-output", "ip6-dvr-reinject",
82 : dd->dd_sw_if_index, 0, 0, 0);
83 : }
84 :
85 7 : dvr_dpo_db[dd->dd_proto][dd->dd_sw_if_index] = INDEX_INVALID;
86 7 : pool_put(dvr_dpo_pool, dd);
87 : }
88 41 : }
89 :
90 : void
91 8 : dvr_dpo_add_or_lock (u32 sw_if_index,
92 : dpo_proto_t dproto,
93 : dpo_id_t *dpo)
94 : {
95 : l2_input_config_t *config;
96 : dvr_dpo_t *dd;
97 :
98 24 : vec_validate_init_empty(dvr_dpo_db[dproto],
99 : sw_if_index,
100 : INDEX_INVALID);
101 :
102 8 : if (INDEX_INVALID == dvr_dpo_db[dproto][sw_if_index])
103 : {
104 7 : dd = dvr_dpo_alloc();
105 :
106 7 : dd->dd_sw_if_index = sw_if_index;
107 7 : dd->dd_proto = dproto;
108 :
109 7 : dvr_dpo_db[dproto][sw_if_index] = dvr_dpo_get_index(dd);
110 :
111 7 : config = l2input_intf_config (sw_if_index);
112 :
113 12 : if (l2_input_is_bridge(config) ||
114 5 : l2_input_is_xconnect(config))
115 : {
116 2 : dd->dd_reinject = DVR_REINJECT_L2;
117 : }
118 : else
119 : {
120 5 : dd->dd_reinject = DVR_REINJECT_L3;
121 : }
122 :
123 : /*
124 : * enable the reinject into L2 path feature on the interface
125 : */
126 7 : if (DPO_PROTO_IP4 == dproto)
127 5 : vnet_feature_enable_disable ("ip4-output", "ip4-dvr-reinject",
128 : dd->dd_sw_if_index, 1, 0, 0);
129 2 : else if (DPO_PROTO_IP6 == dproto)
130 2 : vnet_feature_enable_disable ("ip6-output", "ip6-dvr-reinject",
131 : dd->dd_sw_if_index, 1, 0, 0);
132 : else
133 0 : ASSERT(0);
134 : }
135 : else
136 : {
137 1 : dd = dvr_dpo_get(dvr_dpo_db[dproto][sw_if_index]);
138 : }
139 :
140 8 : dpo_set(dpo, DPO_DVR, dproto, dvr_dpo_get_index(dd));
141 8 : }
142 : #endif /* CLIB_MARCH_VARIANT */
143 :
144 :
145 : static clib_error_t *
146 13514 : dvr_dpo_interface_state_change (vnet_main_t * vnm,
147 : u32 sw_if_index,
148 : u32 flags)
149 : {
150 : /*
151 : */
152 13514 : return (NULL);
153 : }
154 :
155 2881 : VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION(
156 : dvr_dpo_interface_state_change);
157 :
158 : /**
159 : * @brief Registered callback for HW interface state changes
160 : */
161 : static clib_error_t *
162 13336 : dvr_dpo_hw_interface_state_change (vnet_main_t * vnm,
163 : u32 hw_if_index,
164 : u32 flags)
165 : {
166 13336 : return (NULL);
167 : }
168 :
169 2881 : VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION(
170 : dvr_dpo_hw_interface_state_change);
171 :
172 : static clib_error_t *
173 11798 : dvr_dpo_interface_delete (vnet_main_t * vnm,
174 : u32 sw_if_index,
175 : u32 is_add)
176 : {
177 11798 : return (NULL);
178 : }
179 :
180 3459 : VNET_SW_INTERFACE_ADD_DEL_FUNCTION(
181 : dvr_dpo_interface_delete);
182 :
183 : #ifndef CLIB_MARCH_VARIANT
184 : static u8*
185 0 : format_dvr_reinject (u8* s, va_list *ap)
186 : {
187 0 : dvr_dpo_reinject_t ddr = va_arg(*ap, int);
188 :
189 0 : switch (ddr)
190 : {
191 0 : case DVR_REINJECT_L2:
192 0 : s = format (s, "l2");
193 0 : break;
194 0 : case DVR_REINJECT_L3:
195 0 : s = format (s, "l3");
196 0 : break;
197 : }
198 0 : return (s);
199 : }
200 :
201 : static u8*
202 0 : format_dvr_dpo (u8* s, va_list *ap)
203 : {
204 0 : index_t index = va_arg(*ap, index_t);
205 0 : CLIB_UNUSED(u32 indent) = va_arg(*ap, u32);
206 0 : vnet_main_t * vnm = vnet_get_main();
207 0 : dvr_dpo_t *dd = dvr_dpo_get(index);
208 :
209 0 : return (format(s, "%U-dvr-%U-dpo %U",
210 0 : format_dpo_proto, dd->dd_proto,
211 : format_vnet_sw_interface_name,
212 : vnm,
213 : vnet_get_sw_interface(vnm, dd->dd_sw_if_index),
214 0 : format_dvr_reinject, dd->dd_reinject));
215 : }
216 :
217 : static void
218 0 : dvr_dpo_mem_show (void)
219 : {
220 0 : fib_show_memory_usage("DVR",
221 0 : pool_elts(dvr_dpo_pool),
222 0 : pool_len(dvr_dpo_pool),
223 : sizeof(dvr_dpo_t));
224 0 : }
225 :
226 :
227 : const static dpo_vft_t dvr_dpo_vft = {
228 : .dv_lock = dvr_dpo_lock,
229 : .dv_unlock = dvr_dpo_unlock,
230 : .dv_format = format_dvr_dpo,
231 : .dv_mem_show = dvr_dpo_mem_show,
232 : };
233 :
234 : /**
235 : * @brief The per-protocol VLIB graph nodes that are assigned to a glean
236 : * object.
237 : *
238 : * this means that these graph nodes are ones from which a glean is the
239 : * parent object in the DPO-graph.
240 : */
241 : const static char* const dvr_dpo_ip4_nodes[] =
242 : {
243 : "ip4-dvr-dpo",
244 : NULL,
245 : };
246 : const static char* const dvr_dpo_ip6_nodes[] =
247 : {
248 : "ip6-dvr-dpo",
249 : NULL,
250 : };
251 :
252 : const static char* const * const dvr_dpo_nodes[DPO_PROTO_NUM] =
253 : {
254 : [DPO_PROTO_IP4] = dvr_dpo_ip4_nodes,
255 : [DPO_PROTO_IP6] = dvr_dpo_ip6_nodes,
256 : };
257 :
258 : void
259 575 : dvr_dpo_module_init (void)
260 : {
261 575 : dpo_register(DPO_DVR,
262 : &dvr_dpo_vft,
263 : dvr_dpo_nodes);
264 575 : }
265 : #endif /* CLIB_MARCH_VARIANT */
266 :
267 : /**
268 : * @brief Interface DPO trace data
269 : */
270 : typedef struct dvr_dpo_trace_t_
271 : {
272 : u32 sw_if_index;
273 : } dvr_dpo_trace_t;
274 :
275 : always_inline uword
276 13 : dvr_dpo_inline (vlib_main_t * vm,
277 : vlib_node_runtime_t * node,
278 : vlib_frame_t * from_frame,
279 : u8 is_ip6)
280 : {
281 : u32 n_left_from, next_index, * from, * to_next;
282 13 : ip_lookup_main_t *lm = (is_ip6?
283 13 : &ip6_main.lookup_main:
284 : &ip4_main.lookup_main);
285 :
286 13 : from = vlib_frame_vector_args (from_frame);
287 13 : n_left_from = from_frame->n_vectors;
288 :
289 13 : next_index = node->cached_next_index;
290 :
291 26 : while (n_left_from > 0)
292 : {
293 : u32 n_left_to_next;
294 :
295 13 : vlib_get_next_frame(vm, node, next_index, to_next, n_left_to_next);
296 :
297 173 : while (n_left_from >= 4 && n_left_to_next > 2)
298 : {
299 : const dvr_dpo_t *dd0, *dd1;
300 : u32 bi0, ddi0, bi1, ddi1;
301 : vlib_buffer_t *b0, *b1;
302 : u32 next0, next1;
303 : u8 len0, len1;
304 :
305 160 : bi0 = from[0];
306 160 : to_next[0] = bi0;
307 160 : bi1 = from[1];
308 160 : to_next[1] = bi1;
309 160 : from += 2;
310 160 : to_next += 2;
311 160 : n_left_from -= 2;
312 160 : n_left_to_next -= 2;
313 160 : next0 = next1 = 0;
314 :
315 160 : b0 = vlib_get_buffer (vm, bi0);
316 160 : b1 = vlib_get_buffer (vm, bi1);
317 :
318 160 : ddi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX];
319 160 : ddi1 = vnet_buffer(b1)->ip.adj_index[VLIB_TX];
320 160 : dd0 = dvr_dpo_get(ddi0);
321 160 : dd1 = dvr_dpo_get(ddi1);
322 :
323 160 : vnet_buffer(b0)->sw_if_index[VLIB_TX] = dd0->dd_sw_if_index;
324 160 : vnet_buffer(b1)->sw_if_index[VLIB_TX] = dd1->dd_sw_if_index;
325 :
326 160 : len0 = ((u8*)vlib_buffer_get_current(b0) -
327 160 : (u8*)ethernet_buffer_get_header(b0));
328 160 : len1 = ((u8*)vlib_buffer_get_current(b1) -
329 160 : (u8*)ethernet_buffer_get_header(b1));
330 160 : vnet_buffer(b0)->l2.l2_len =
331 160 : vnet_buffer(b0)->ip.save_rewrite_length =
332 : len0;
333 160 : vnet_buffer(b1)->l2.l2_len =
334 160 : vnet_buffer(b1)->ip.save_rewrite_length =
335 : len1;
336 :
337 160 : b0->flags |= VNET_BUFFER_F_IS_DVR;
338 160 : b1->flags |= VNET_BUFFER_F_IS_DVR;
339 :
340 160 : vlib_buffer_advance(b0, -len0);
341 160 : vlib_buffer_advance(b1, -len1);
342 :
343 160 : vnet_feature_arc_start (lm->output_feature_arc_index,
344 : dd0->dd_sw_if_index, &next0, b0);
345 160 : vnet_feature_arc_start (lm->output_feature_arc_index,
346 : dd1->dd_sw_if_index, &next1, b1);
347 :
348 160 : if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
349 : {
350 : dvr_dpo_trace_t *tr0;
351 :
352 160 : tr0 = vlib_add_trace (vm, node, b0, sizeof (*tr0));
353 160 : tr0->sw_if_index = dd0->dd_sw_if_index;
354 : }
355 160 : if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED))
356 : {
357 : dvr_dpo_trace_t *tr1;
358 :
359 160 : tr1 = vlib_add_trace (vm, node, b1, sizeof (*tr1));
360 160 : tr1->sw_if_index = dd1->dd_sw_if_index;
361 : }
362 :
363 160 : vlib_validate_buffer_enqueue_x2(vm, node, next_index, to_next,
364 : n_left_to_next, bi0, bi1,
365 : next0, next1);
366 : }
367 :
368 36 : while (n_left_from > 0 && n_left_to_next > 0)
369 : {
370 : const dvr_dpo_t * dd0;
371 : vlib_buffer_t * b0;
372 : u32 bi0, ddi0;
373 : u32 next0;
374 : u8 len0;
375 :
376 23 : bi0 = from[0];
377 23 : to_next[0] = bi0;
378 23 : from += 1;
379 23 : to_next += 1;
380 23 : n_left_from -= 1;
381 23 : n_left_to_next -= 1;
382 23 : next0 = 0;
383 :
384 23 : b0 = vlib_get_buffer (vm, bi0);
385 :
386 23 : ddi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX];
387 23 : dd0 = dvr_dpo_get(ddi0);
388 :
389 23 : vnet_buffer(b0)->sw_if_index[VLIB_TX] = dd0->dd_sw_if_index;
390 :
391 : /*
392 : * take that, rewind it back...
393 : */
394 23 : len0 = ((u8*)vlib_buffer_get_current(b0) -
395 23 : (u8*)ethernet_buffer_get_header(b0));
396 23 : vnet_buffer(b0)->l2.l2_len =
397 23 : vnet_buffer(b0)->ip.save_rewrite_length =
398 : len0;
399 23 : b0->flags |= VNET_BUFFER_F_IS_DVR;
400 23 : vlib_buffer_advance(b0, -len0);
401 :
402 : /*
403 : * start processing the ipX output features
404 : */
405 23 : vnet_feature_arc_start(lm->output_feature_arc_index,
406 : dd0->dd_sw_if_index, &next0, b0);
407 :
408 23 : if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
409 : {
410 : dvr_dpo_trace_t *tr;
411 :
412 23 : tr = vlib_add_trace (vm, node, b0, sizeof (*tr));
413 23 : tr->sw_if_index = dd0->dd_sw_if_index;
414 : }
415 :
416 23 : vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next,
417 : n_left_to_next, bi0,
418 : next0);
419 : }
420 13 : vlib_put_next_frame (vm, node, next_index, n_left_to_next);
421 : }
422 13 : return from_frame->n_vectors;
423 : }
424 :
425 : static u8 *
426 533 : format_dvr_dpo_trace (u8 * s, va_list * args)
427 : {
428 533 : CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
429 533 : CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
430 533 : dvr_dpo_trace_t * t = va_arg (*args, dvr_dpo_trace_t *);
431 533 : u32 indent = format_get_indent (s);
432 533 : s = format (s, "%U sw_if_index:%d",
433 : format_white_space, indent,
434 : t->sw_if_index);
435 533 : return s;
436 : }
437 :
438 2313 : VLIB_NODE_FN (ip4_dvr_dpo_node) (vlib_main_t * vm,
439 : vlib_node_runtime_t * node,
440 : vlib_frame_t * from_frame)
441 : {
442 13 : return (dvr_dpo_inline(vm, node, from_frame, 0));
443 : }
444 :
445 2300 : VLIB_NODE_FN (ip6_dvr_dpo_node) (vlib_main_t * vm,
446 : vlib_node_runtime_t * node,
447 : vlib_frame_t * from_frame)
448 : {
449 0 : return (dvr_dpo_inline(vm, node, from_frame, 1));
450 : }
451 :
452 183788 : VLIB_REGISTER_NODE (ip4_dvr_dpo_node) = {
453 : .name = "ip4-dvr-dpo",
454 : .vector_size = sizeof (u32),
455 : .format_trace = format_dvr_dpo_trace,
456 : .sibling_of = "ip4-rewrite",
457 : };
458 183788 : VLIB_REGISTER_NODE (ip6_dvr_dpo_node) = {
459 : .name = "ip6-dvr-dpo",
460 : .vector_size = sizeof (u32),
461 : .format_trace = format_dvr_dpo_trace,
462 : .sibling_of = "ip6-rewrite",
463 : };
464 :
465 : typedef enum dvr_reinject_next_t_
466 : {
467 : DVR_REINJECT_NEXT_L2,
468 : DVR_REINJECT_NEXT_L3,
469 : } dvr_reinject_next_t;
470 :
471 : always_inline uword
472 12 : dvr_reinject_inline (vlib_main_t * vm,
473 : vlib_node_runtime_t * node,
474 : vlib_frame_t * from_frame)
475 : {
476 : u32 n_left_from, next_index, * from, * to_next;
477 :
478 12 : from = vlib_frame_vector_args (from_frame);
479 12 : n_left_from = from_frame->n_vectors;
480 :
481 12 : next_index = node->cached_next_index;
482 :
483 24 : while (n_left_from > 0)
484 : {
485 : u32 n_left_to_next;
486 :
487 12 : vlib_get_next_frame(vm, node, next_index, to_next, n_left_to_next);
488 :
489 140 : while (n_left_from >= 4 && n_left_to_next > 2)
490 : {
491 : dvr_reinject_next_t next0, next1;
492 : const dvr_dpo_t *dd0, *dd1;
493 : u32 bi0, bi1, ddi0, ddi1;
494 : vlib_buffer_t *b0, *b1;
495 :
496 128 : bi0 = from[0];
497 128 : to_next[0] = bi0;
498 128 : bi1 = from[1];
499 128 : to_next[1] = bi1;
500 128 : from += 2;
501 128 : to_next += 2;
502 128 : n_left_from -= 2;
503 128 : n_left_to_next -= 2;
504 :
505 128 : b0 = vlib_get_buffer (vm, bi0);
506 128 : b1 = vlib_get_buffer (vm, bi1);
507 :
508 128 : if (b0->flags & VNET_BUFFER_F_IS_DVR)
509 : {
510 128 : ddi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX];
511 128 : dd0 = dvr_dpo_get(ddi0);
512 128 : next0 = (dd0->dd_reinject == DVR_REINJECT_L2 ?
513 128 : DVR_REINJECT_NEXT_L2 :
514 : DVR_REINJECT_NEXT_L3);
515 : }
516 : else
517 0 : vnet_feature_next( &next0, b0);
518 :
519 128 : if (b1->flags & VNET_BUFFER_F_IS_DVR)
520 : {
521 128 : ddi1 = vnet_buffer(b1)->ip.adj_index[VLIB_TX];
522 128 : dd1 = dvr_dpo_get(ddi1);
523 128 : next1 = (dd1->dd_reinject == DVR_REINJECT_L2 ?
524 128 : DVR_REINJECT_NEXT_L2 :
525 : DVR_REINJECT_NEXT_L3);
526 : }
527 : else
528 0 : vnet_feature_next( &next1, b1);
529 :
530 128 : if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
531 : {
532 : dvr_dpo_trace_t *tr0;
533 :
534 128 : tr0 = vlib_add_trace (vm, node, b0, sizeof (*tr0));
535 128 : tr0->sw_if_index = vnet_buffer(b0)->sw_if_index[VLIB_TX];
536 : }
537 128 : if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED))
538 : {
539 : dvr_dpo_trace_t *tr1;
540 :
541 128 : tr1 = vlib_add_trace (vm, node, b1, sizeof (*tr1));
542 128 : tr1->sw_if_index = vnet_buffer(b1)->sw_if_index[VLIB_TX];
543 : }
544 :
545 128 : vlib_validate_buffer_enqueue_x2(vm, node, next_index, to_next,
546 : n_left_to_next, bi0, bi1,
547 : next0, next1);
548 : }
549 :
550 32 : while (n_left_from > 0 && n_left_to_next > 0)
551 : {
552 : dvr_reinject_next_t next0;
553 : const dvr_dpo_t *dd0;
554 : vlib_buffer_t * b0;
555 : u32 bi0, ddi0;
556 :
557 20 : bi0 = from[0];
558 20 : to_next[0] = bi0;
559 20 : from += 1;
560 20 : to_next += 1;
561 20 : n_left_from -= 1;
562 20 : n_left_to_next -= 1;
563 :
564 20 : b0 = vlib_get_buffer (vm, bi0);
565 :
566 20 : if (b0->flags & VNET_BUFFER_F_IS_DVR)
567 : {
568 20 : ddi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX];
569 20 : dd0 = dvr_dpo_get(ddi0);
570 20 : next0 = (dd0->dd_reinject == DVR_REINJECT_L2 ?
571 20 : DVR_REINJECT_NEXT_L2 :
572 : DVR_REINJECT_NEXT_L3);
573 : }
574 : else
575 0 : vnet_feature_next( &next0, b0);
576 :
577 20 : if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED))
578 : {
579 : dvr_dpo_trace_t *tr;
580 :
581 20 : tr = vlib_add_trace (vm, node, b0, sizeof (*tr));
582 20 : tr->sw_if_index = vnet_buffer(b0)->sw_if_index[VLIB_TX];
583 : }
584 :
585 20 : vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next,
586 : n_left_to_next, bi0, next0);
587 : }
588 12 : vlib_put_next_frame (vm, node, next_index, n_left_to_next);
589 : }
590 12 : return from_frame->n_vectors;
591 : }
592 :
593 2312 : VLIB_NODE_FN (ip4_dvr_reinject_node) (vlib_main_t * vm,
594 : vlib_node_runtime_t * node,
595 : vlib_frame_t * from_frame)
596 : {
597 12 : return (dvr_reinject_inline(vm, node, from_frame));
598 : }
599 :
600 2300 : VLIB_NODE_FN (ip6_dvr_reinject_node) (vlib_main_t * vm,
601 : vlib_node_runtime_t * node,
602 : vlib_frame_t * from_frame)
603 : {
604 0 : return (dvr_reinject_inline(vm, node, from_frame));
605 : }
606 :
607 183788 : VLIB_REGISTER_NODE (ip4_dvr_reinject_node) = {
608 : .name = "ip4-dvr-reinject",
609 : .vector_size = sizeof (u32),
610 : .format_trace = format_dvr_dpo_trace,
611 :
612 : .n_next_nodes = 1,
613 : .next_nodes = {
614 : [DVR_REINJECT_NEXT_L2] = "l2-output",
615 : [DVR_REINJECT_NEXT_L3] = "interface-output",
616 : },
617 : };
618 :
619 183788 : VLIB_REGISTER_NODE (ip6_dvr_reinject_node) = {
620 : .name = "ip6-dvr-reinject",
621 : .vector_size = sizeof (u32),
622 : .format_trace = format_dvr_dpo_trace,
623 :
624 : .n_next_nodes = 1,
625 : .next_nodes = {
626 : [DVR_REINJECT_NEXT_L2] = "l2-output",
627 : [DVR_REINJECT_NEXT_L3] = "interface-output",
628 : },
629 : };
630 :
631 76635 : VNET_FEATURE_INIT (ip4_dvr_reinject_feat_node, static) =
632 : {
633 : .arc_name = "ip4-output",
634 : .node_name = "ip4-dvr-reinject",
635 : };
636 76635 : VNET_FEATURE_INIT (ip6_dvr_reinject_feat_node, static) =
637 : {
638 : .arc_name = "ip6-output",
639 : .node_name = "ip6-dvr-reinject",
640 : };
641 :
|