2 * vrrp.c - vrrp plugin action functions
4 * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
6 * SPDX-License-Identifier: Apache-2.0
10 #include <vnet/vnet.h>
11 #include <vnet/plugin/plugin.h>
12 #include <vnet/mfib/mfib_entry.h>
13 #include <vnet/mfib/mfib_table.h>
14 #include <vnet/adj/adj.h>
15 #include <vnet/adj/adj_mcast.h>
16 #include <vnet/fib/fib_table.h>
17 #include <vnet/fib/fib_sas.h>
18 #include <vnet/ip/igmp_packet.h>
19 #include <vnet/ip/ip6_link.h>
20 #include <vnet/ethernet/arp_packet.h>
22 #include <vrrp/vrrp.h>
23 #include <vrrp/vrrp_packet.h>
25 #include <vpp/app/version.h>
27 static const u8 vrrp4_dst_mac[6] = { 0x1, 0x0, 0x5e, 0x0, 0x0, 0x12 };
28 static const u8 vrrp6_dst_mac[6] = { 0x33, 0x33, 0x0, 0x0, 0x0, 0x12 };
29 static const u8 vrrp_src_mac_prefix[4] = { 0x0, 0x0, 0x5e, 0x0 };
32 vrrp_adv_l2_build_multicast (vrrp_vr_t * vr, vlib_buffer_t * b)
34 vnet_main_t *vnm = vnet_get_main ();
35 vnet_link_t link_type;
36 ethernet_header_t *eth;
42 eth = vlib_buffer_get_current (b);
44 if (vrrp_vr_is_ipv6 (vr))
46 dst_mac = vrrp6_dst_mac;
47 link_type = VNET_LINK_IP6;
52 dst_mac = vrrp4_dst_mac;
53 link_type = VNET_LINK_IP4;
57 rewrite = ethernet_build_rewrite (vnm, vr->config.sw_if_index, link_type,
59 clib_memcpy (eth, rewrite, vec_len (rewrite));
61 /* change the source mac from the HW addr to the VRRP virtual MAC */
63 (eth->src_address, vrrp_src_mac_prefix, sizeof (vrrp_src_mac_prefix));
64 eth->src_address[4] = mac_byte_ipver;
65 eth->src_address[5] = vr->config.vr_id;
67 n_bytes += vec_len (rewrite);
69 vlib_buffer_chain_increase_length (b, b, n_bytes);
70 vlib_buffer_advance (b, n_bytes);
77 #define VRRP4_MCAST_ADDR_AS_U8 { 224, 0, 0, 18 }
78 #define VRRP6_MCAST_ADDR_AS_U8 \
79 { 0xff, 0x2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12 }
81 static const ip46_address_t vrrp4_mcast_addr = {
82 .ip4 = {.as_u8 = VRRP4_MCAST_ADDR_AS_U8,},
85 static const ip46_address_t vrrp6_mcast_addr = {
86 .ip6 = {.as_u8 = VRRP6_MCAST_ADDR_AS_U8,},
89 /* size of static parts of header + (# addrs * addr length) */
91 vrrp_adv_payload_len (vrrp_vr_t * vr)
93 u16 addr_len = vrrp_vr_is_ipv6 (vr) ? 16 : 4;
95 return sizeof (vrrp_header_t) + (vec_len (vr->config.vr_addrs) * addr_len);
99 vrrp_adv_l3_build (vrrp_vr_t * vr, vlib_buffer_t * b,
100 const ip46_address_t * dst)
102 if (!vrrp_vr_is_ipv6 (vr)) /* IPv4 */
104 ip4_header_t *ip4 = vlib_buffer_get_current (b);
107 clib_memset (ip4, 0, sizeof (*ip4));
108 ip4->ip_version_and_header_length = 0x45;
110 ip4->protocol = IP_PROTOCOL_VRRP;
111 clib_memcpy (&ip4->dst_address, &dst->ip4, sizeof (dst->ip4));
113 /* RFC 5798 Section 5.1.1.1 - Source Address "is the primary IPv4
114 * address of the interface the packet is being sent from". Assume
115 * this is the first address on the interface.
117 src4 = ip_interface_get_first_ip (vr->config.sw_if_index, 1);
122 ip4->src_address.as_u32 = src4->as_u32;
123 ip4->length = clib_host_to_net_u16 (sizeof (*ip4) +
124 vrrp_adv_payload_len (vr));
125 ip4->checksum = ip4_header_checksum (ip4);
127 vlib_buffer_chain_increase_length (b, b, sizeof (*ip4));
128 vlib_buffer_advance (b, sizeof (*ip4));
130 return sizeof (*ip4);
134 ip6_header_t *ip6 = vlib_buffer_get_current (b);
136 clib_memset (ip6, 0, sizeof (*ip6));
137 ip6->ip_version_traffic_class_and_flow_label = 0x00000060;
138 ip6->hop_limit = 255;
139 ip6->protocol = IP_PROTOCOL_VRRP;
140 clib_memcpy (&ip6->dst_address, &dst->ip6, sizeof (dst->ip6));
141 ip6_address_copy (&ip6->src_address,
142 ip6_get_link_local_address (vr->config.sw_if_index));
143 ip6->payload_length = clib_host_to_net_u16 (vrrp_adv_payload_len (vr));
145 vlib_buffer_chain_increase_length (b, b, sizeof (*ip6));
146 vlib_buffer_advance (b, sizeof (*ip6));
148 return sizeof (*ip6);
154 vrrp_adv_csum (void *l3_hdr, void *payload, u8 is_ipv6, u16 len)
157 u8 proto = IP_PROTOCOL_VRRP;
159 int word_size = sizeof (uword);
166 src_addr = &(((ip6_header_t *) l3_hdr)->src_address);
171 src_addr = &(((ip4_header_t *) l3_hdr)->src_address);
174 for (i = 0; i < (2 * addr_len); i += word_size)
176 if (word_size == sizeof (u64))
178 ip_csum_with_carry (csum, clib_mem_unaligned (src_addr + i, u64));
181 ip_csum_with_carry (csum, clib_mem_unaligned (src_addr + i, u32));
184 csum = ip_csum_with_carry (csum,
185 clib_host_to_net_u32 (len + (proto << 16)));
187 /* now do the payload */
188 csum = ip_incremental_checksum (csum, payload, len);
190 csum = ~ip_csum_fold (csum);
196 vrrp_adv_payload_build (vrrp_vr_t * vr, vlib_buffer_t * b, int shutdown)
198 vrrp_header_t *vrrp = vlib_buffer_get_current (b);
200 ip46_address_t *vr_addr;
206 n_addrs = vec_len (vr->config.vr_addrs);
207 is_ipv6 = vrrp_vr_is_ipv6 (vr);
213 len = sizeof (*vrrp) + n_addrs * sizeof (ip6_address_t);;
214 l3_hdr = vlib_buffer_get_current (b) - sizeof (ip6_header_t);
216 ip6->payload_length = clib_host_to_net_u16 (len);
220 len = sizeof (*vrrp) + n_addrs * sizeof (ip4_address_t);
221 l3_hdr = vlib_buffer_get_current (b) - sizeof (ip4_header_t);
224 vrrp->vrrp_version_and_type = 0x31;
225 vrrp->vr_id = vr->config.vr_id;
226 vrrp->priority = (shutdown) ? 0 : vrrp_vr_priority (vr);
227 vrrp->n_addrs = vec_len (vr->config.vr_addrs);
228 vrrp->rsvd_and_max_adv_int = clib_host_to_net_u16 (vr->config.adv_interval);
231 hdr_addr = (void *) (vrrp + 1);
233 vec_foreach (vr_addr, vr->config.vr_addrs)
237 clib_memcpy (hdr_addr, &vr_addr->ip6, 16);
242 clib_memcpy (hdr_addr, &vr_addr->ip4, 4);
247 vlib_buffer_chain_increase_length (b, b, vrrp_adv_payload_len (vr));
250 vrrp_adv_csum (l3_hdr, vrrp, is_ipv6, vrrp_adv_payload_len (vr));
255 static_always_inline u32
256 vrrp_adv_next_node (vrrp_vr_t * vr)
258 if (vrrp_vr_is_unicast (vr))
260 if (vrrp_vr_is_ipv6 (vr))
261 return ip6_lookup_node.index;
263 return ip4_lookup_node.index;
267 vrrp_main_t *vmp = &vrrp_main;
269 return vmp->intf_output_node_idx;
273 static_always_inline const ip46_address_t *
274 vrrp_adv_mcast_addr (vrrp_vr_t * vr)
276 if (vrrp_vr_is_ipv6 (vr))
277 return &vrrp6_mcast_addr;
279 return &vrrp4_mcast_addr;
283 vrrp_adv_send (vrrp_vr_t * vr, int shutdown)
285 vlib_main_t *vm = vlib_get_main ();
286 vlib_frame_t *to_frame;
287 int i, n_buffers = 1;
288 u32 node_index, *to_next, *bi = 0;
289 u8 is_unicast = vrrp_vr_is_unicast (vr);
291 node_index = vrrp_adv_next_node (vr);
294 n_buffers = vec_len (vr->config.peer_addrs);
298 /* A unicast VR will not start without peers added so this should
299 * not happen. Just avoiding a crash if it happened somehow.
301 clib_warning ("Unicast VR configuration corrupted for %U",
302 format_vrrp_vr_key, vr);
306 vec_validate (bi, n_buffers - 1);
307 if (vlib_buffer_alloc (vm, bi, n_buffers) != n_buffers)
309 clib_warning ("Buffer allocation failed for %U", format_vrrp_vr_key,
315 to_frame = vlib_get_frame_to_node (vm, node_index);
316 to_next = vlib_frame_vector_args (to_frame);
318 for (i = 0; i < n_buffers; i++)
322 const ip46_address_t *dst = vrrp_adv_mcast_addr (vr);
324 bi0 = vec_elt (bi, i);
325 b = vlib_get_buffer (vm, bi0);
327 b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
328 vnet_buffer (b)->sw_if_index[VLIB_RX] = 0;
329 vnet_buffer (b)->sw_if_index[VLIB_TX] = vr->config.sw_if_index;
333 dst = vec_elt_at_index (vr->config.peer_addrs, i);
334 vnet_buffer (b)->sw_if_index[VLIB_TX] = ~0;
337 vrrp_adv_l2_build_multicast (vr, b);
339 if (-1 == vrrp_adv_l3_build (vr, b, dst))
341 vlib_frame_free (vm, to_frame);
342 vlib_buffer_free (vm, bi, n_buffers);
345 vrrp_adv_payload_build (vr, b, shutdown);
347 vlib_buffer_reset (b);
352 to_frame->n_vectors = n_buffers;
354 vlib_put_frame_to_node (vm, node_index, to_frame);
356 vrrp_incr_stat_counter (VRRP_STAT_COUNTER_ADV_SENT, vr->stat_index);
359 vrrp_incr_stat_counter (VRRP_STAT_COUNTER_PRIO0_SENT, vr->stat_index);
368 vrrp6_na_pkt_build (vrrp_vr_t * vr, vlib_buffer_t * b, ip6_address_t * addr6)
370 vnet_main_t *vnm = vnet_get_main ();
371 vlib_main_t *vm = vlib_get_main ();
372 ethernet_header_t *eth;
374 icmp6_neighbor_solicitation_or_advertisement_header_t *na;
375 icmp6_neighbor_discovery_ethernet_link_layer_address_option_t *ll_opt;
376 int payload_length, bogus_length;
377 int rewrite_bytes = 0;
382 eth = vlib_buffer_get_current (b);
384 ip6_multicast_ethernet_address (dst_mac, IP6_MULTICAST_GROUP_ID_all_hosts);
386 ethernet_build_rewrite (vnm, vr->config.sw_if_index, VNET_LINK_IP6,
388 rewrite_bytes += vec_len (rewrite);
389 clib_memcpy (eth, rewrite, vec_len (rewrite));
392 b->current_length += rewrite_bytes;
393 vlib_buffer_advance (b, rewrite_bytes);
396 ip6 = vlib_buffer_get_current (b);
398 b->current_length += sizeof (*ip6);
399 clib_memset (ip6, 0, sizeof (*ip6));
401 ip6->ip_version_traffic_class_and_flow_label = 0x00000060;
402 ip6->protocol = IP_PROTOCOL_ICMP6;
403 ip6->hop_limit = 255;
404 ip6_set_reserved_multicast_address (&ip6->dst_address,
405 IP6_MULTICAST_SCOPE_link_local,
406 IP6_MULTICAST_GROUP_ID_all_hosts);
407 ip6_address_copy (&ip6->src_address,
408 ip6_get_link_local_address (vr->config.sw_if_index));
412 na = (icmp6_neighbor_solicitation_or_advertisement_header_t *) (ip6 + 1);
414 (icmp6_neighbor_discovery_ethernet_link_layer_address_option_t *) (na +
417 payload_length = sizeof (*na) + sizeof (*ll_opt);
418 b->current_length += payload_length;
419 clib_memset (na, 0, payload_length);
421 na->icmp.type = ICMP6_neighbor_advertisement; /* icmp code, csum are 0 */
422 na->target_address = *addr6;
423 na->advertisement_flags = clib_host_to_net_u32
424 (ICMP6_NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE
425 | ICMP6_NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER);
427 ll_opt->header.type =
428 ICMP6_NEIGHBOR_DISCOVERY_OPTION_target_link_layer_address;
429 ll_opt->header.n_data_u64s = 1;
430 clib_memcpy (ll_opt->ethernet_address, vr->runtime.mac.bytes,
431 sizeof (vr->runtime.mac));
433 ip6->payload_length = clib_host_to_net_u16 (payload_length);
435 ip6_tcp_udp_icmp_compute_checksum (vm, b, ip6, &bogus_length);
438 const mac_address_t broadcast_mac = {
439 .bytes = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff,},
443 vrrp4_garp_pkt_build (vrrp_vr_t * vr, vlib_buffer_t * b, ip4_address_t * ip4)
445 vnet_main_t *vnm = vnet_get_main ();
446 ethernet_header_t *eth;
447 ethernet_arp_header_t *arp;
451 eth = vlib_buffer_get_current (b);
454 ethernet_build_rewrite (vnm, vr->config.sw_if_index, VNET_LINK_ARP,
455 broadcast_mac.bytes);
456 rewrite_bytes = vec_len (rewrite);
457 clib_memcpy (eth, rewrite, rewrite_bytes);
460 b->current_length += rewrite_bytes;
461 vlib_buffer_advance (b, rewrite_bytes);
463 arp = vlib_buffer_get_current (b);
464 b->current_length += sizeof (*arp);
466 clib_memset (arp, 0, sizeof (*arp));
468 arp->l2_type = clib_host_to_net_u16 (ETHERNET_ARP_HARDWARE_TYPE_ethernet);
469 arp->l3_type = clib_host_to_net_u16 (ETHERNET_TYPE_IP4);
470 arp->n_l2_address_bytes = 6;
471 arp->n_l3_address_bytes = 4;
472 arp->opcode = clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_request);
473 arp->ip4_over_ethernet[0].mac = vr->runtime.mac;
474 arp->ip4_over_ethernet[0].ip4 = *ip4;
475 arp->ip4_over_ethernet[1].mac = broadcast_mac;
476 arp->ip4_over_ethernet[1].ip4 = *ip4;
480 vrrp_garp_or_na_send (vrrp_vr_t * vr)
482 vlib_main_t *vm = vlib_get_main ();
483 vrrp_main_t *vmp = &vrrp_main;
484 vlib_frame_t *to_frame;
490 if (vec_len (vr->config.peer_addrs))
491 return 0; /* unicast is used in routed environments - don't garp */
493 n_buffers = vec_len (vr->config.vr_addrs);
496 clib_warning ("Unable to send gratuitous ARP for VR %U - no addresses",
497 format_vrrp_vr_key, vr);
501 /* need to send a packet for each VR address */
502 vec_validate (bi, n_buffers - 1);
504 if (vlib_buffer_alloc (vm, bi, n_buffers) != n_buffers)
506 clib_warning ("Buffer allocation failed for %U", format_vrrp_vr_key,
512 to_frame = vlib_get_frame_to_node (vm, vmp->intf_output_node_idx);
513 to_frame->n_vectors = 0;
514 to_next = vlib_frame_vector_args (to_frame);
516 for (i = 0; i < n_buffers; i++)
519 ip46_address_t *addr;
521 addr = vec_elt_at_index (vr->config.vr_addrs, i);
522 b = vlib_get_buffer (vm, bi[i]);
524 b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
525 vnet_buffer (b)->sw_if_index[VLIB_RX] = 0;
526 vnet_buffer (b)->sw_if_index[VLIB_TX] = vr->config.sw_if_index;
528 if (vrrp_vr_is_ipv6 (vr))
529 vrrp6_na_pkt_build (vr, b, &addr->ip6);
531 vrrp4_garp_pkt_build (vr, b, &addr->ip4);
533 vlib_buffer_reset (b);
536 to_frame->n_vectors++;
539 vlib_put_frame_to_node (vm, vmp->intf_output_node_idx, to_frame);
546 #define IGMP4_MCAST_ADDR_AS_U8 { 224, 0, 0, 22 }
548 static const ip4_header_t igmp_ip4_mcast = {
549 .ip_version_and_header_length = 0x46, /* there's options! */
551 .protocol = IP_PROTOCOL_IGMP,
553 .dst_address = {.as_u8 = IGMP4_MCAST_ADDR_AS_U8,},
557 vrrp_igmp_pkt_build (vrrp_vr_t *vr, vlib_buffer_t *b)
561 igmp_membership_report_v3_t *report;
562 igmp_membership_group_v3_t *group;
565 ip4 = vlib_buffer_get_current (b);
566 clib_memcpy (ip4, &igmp_ip4_mcast, sizeof (*ip4));
568 /* Use the source address advertisements will use to join mcast group */
569 src4 = ip_interface_get_first_ip (vr->config.sw_if_index, 1);
574 ip4->src_address.as_u32 = src4->as_u32;
576 vlib_buffer_chain_increase_length (b, b, sizeof (*ip4));
577 vlib_buffer_advance (b, sizeof (*ip4));
579 ip4_options = (u8 *) (ip4 + 1);
580 ip4_options[0] = 0x94; /* 10010100 == the router alert option */
581 ip4_options[1] = 0x04; /* length == 4 bytes */
582 ip4_options[2] = 0x0; /* value == Router shall examine packet */
583 ip4_options[3] = 0x0; /* reserved */
585 vlib_buffer_chain_increase_length (b, b, 4);
586 vlib_buffer_advance (b, 4);
588 report = vlib_buffer_get_current (b);
590 report->header.type = IGMP_TYPE_membership_report_v3;
591 report->header.code = 0;
592 report->header.checksum = 0;
594 report->n_groups = clib_host_to_net_u16 (1);
596 vlib_buffer_chain_increase_length (b, b, sizeof (*report));
597 vlib_buffer_advance (b, sizeof (*report));
599 group = vlib_buffer_get_current (b);
600 group->type = IGMP_MEMBERSHIP_GROUP_change_to_exclude;
601 group->n_aux_u32s = 0;
602 group->n_src_addresses = 0;
603 group->group_address.as_u32 = clib_host_to_net_u32 (0xe0000012);
605 vlib_buffer_chain_increase_length (b, b, sizeof (*group));
606 vlib_buffer_advance (b, sizeof (*group));
608 ip4->length = clib_host_to_net_u16 (b->current_data);
609 ip4->checksum = ip4_header_checksum (ip4);
611 int payload_len = vlib_buffer_get_current (b) - ((void *) report);
612 report->header.checksum =
613 ~ip_csum_fold (ip_incremental_checksum (0, report, payload_len));
615 vlib_buffer_reset (b);
619 /* multicast listener report packet format for ethernet. */
620 typedef CLIB_PACKED (struct
622 ip6_hop_by_hop_ext_t ext_hdr;
623 ip6_router_alert_option_t alert;
624 ip6_padN_option_t pad;
625 icmp46_header_t icmp;
627 u16 num_addr_records;
628 icmp6_multicast_address_record_t records[0];
629 }) icmp6_multicast_listener_report_header_t;
632 vrrp_icmp6_mlr_pkt_build (vrrp_vr_t * vr, vlib_buffer_t * b)
634 vlib_main_t *vm = vlib_get_main ();
636 icmp6_multicast_listener_report_header_t *rh;
637 icmp6_multicast_address_record_t *rr;
638 ip46_address_t *vr_addr;
639 int bogus_length, n_addrs;
642 n_addrs = vec_len (vr->config.vr_addrs) + 1;
643 payload_length = sizeof (*rh) + (n_addrs * sizeof (*rr));
644 b->current_length = sizeof (*ip6) + payload_length;
645 b->error = ICMP6_ERROR_NONE;
647 ip6 = vlib_buffer_get_current (b);
648 rh = (icmp6_multicast_listener_report_header_t *) (ip6 + 1);
649 rr = (icmp6_multicast_address_record_t *) (rh + 1);
652 clib_memset (ip6, 0, b->current_length);
653 ip6->ip_version_traffic_class_and_flow_label =
654 clib_host_to_net_u32 (0x60000000);
656 ip6->protocol = IP_PROTOCOL_IP6_HOP_BY_HOP_OPTIONS;
657 ip6_set_reserved_multicast_address (&ip6->dst_address,
658 IP6_MULTICAST_SCOPE_link_local,
659 IP6_MULTICAST_GROUP_ID_mldv2_routers);
660 ip6_address_copy (&ip6->src_address,
661 ip6_get_link_local_address (vr->config.sw_if_index));
663 clib_memset (rh, 0, sizeof (*rh));
665 /* v6 hop by hop extension header */
666 rh->ext_hdr.next_hdr = IP_PROTOCOL_ICMP6;
667 rh->ext_hdr.n_data_u64s = 0;
669 rh->alert.type = IP6_MLDP_ALERT_TYPE;
677 rh->icmp.type = ICMP6_multicast_listener_report_v2;
678 rh->icmp.checksum = 0;
681 rh->num_addr_records = clib_host_to_net_u16 (n_addrs);
683 /* group addresses */
685 /* All VRRP routers group */
687 rr->aux_data_len_u32s = 0;
690 (&rr->mcast_addr, &vrrp6_mcast_addr.ip6, sizeof (ip6_address_t));
692 /* solicited node multicast addresses for VR addrs */
693 vec_foreach (vr_addr, vr->config.vr_addrs)
699 rr->aux_data_len_u32s = 0;
702 id = clib_net_to_host_u32 (vr_addr->ip6.as_u32[3]) & 0x00ffffff;
703 ip6_set_solicited_node_multicast_address (&rr->mcast_addr, id);
706 ip6->payload_length = clib_host_to_net_u16 (payload_length);
707 rh->icmp.checksum = ip6_tcp_udp_icmp_compute_checksum (vm, b, ip6,
712 vrrp_vr_multicast_group_join (vrrp_vr_t * vr)
714 vlib_main_t *vm = vlib_get_main ();
717 vnet_main_t *vnm = vnet_get_main ();
719 u32 bi = 0, *to_next;
724 if (!vnet_sw_interface_is_up (vnm, vr->config.sw_if_index))
727 is_ipv6 = vrrp_vr_is_ipv6 (vr);
729 if (is_ipv6 && ip6_link_is_enabled (vr->config.sw_if_index) == 0)
732 if (vlib_buffer_alloc (vm, &bi, n_buffers) != n_buffers)
734 clib_warning ("Buffer allocation failed for %U", format_vrrp_vr_key,
739 b = vlib_get_buffer (vm, bi);
741 b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
743 vnet_buffer (b)->sw_if_index[VLIB_RX] = 0;
744 vnet_buffer (b)->sw_if_index[VLIB_TX] = vr->config.sw_if_index;
746 intf = vrrp_intf_get (vr->config.sw_if_index);
747 vnet_buffer (b)->ip.adj_index[VLIB_TX] = intf->mcast_adj_index[is_ipv6];
751 vrrp_icmp6_mlr_pkt_build (vr, b);
752 node_index = ip6_rewrite_mcast_node.index;
756 if (-1 == vrrp_igmp_pkt_build (vr, b))
758 clib_warning ("IGMP packet build failed for %U", format_vrrp_vr_key,
760 vlib_buffer_free (vm, &bi, 1);
763 node_index = ip4_rewrite_mcast_node.index;
766 f = vlib_get_frame_to_node (vm, node_index);
767 to_next = vlib_frame_vector_args (f);
771 vlib_put_frame_to_node (vm, node_index, f);
778 * fd.io coding-style-patch-verification: ON
781 * eval: (c-set-style "gnu")