/* * Copyright (c) 2017 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * ioam_cache_tunnel_select_node.c * This file implements anycast server selection using ioam data * attached to anycast service selection. * Anycast service is reachable via multiple servers reachable * over SR tunnels. * Works with TCP Anycast application. * Cache entry is created when TCP SYN is received for anycast destination. * Response TCP SYN ACKs for anycast service is compared and selected * response is forwarded. * The functionality is introduced via graph nodes that are hooked into * vnet graph via classifier configs like below: * * Enable anycast service selection: * set ioam ip6 sr-tunnel-select oneway * * Enable following classifier on the anycast service client facing interface * e.g. anycast service is db06::06 then: * classify session acl-hit-next ip6-node ip6-add-syn-hop-by-hop table-index 0 match l3 * ip6 dst db06::06 ioam-encap anycast * * Enable following classifier on the interfaces facing the server of anycast service: * classify session acl-hit-next ip6-node ip6-lookup table-index 0 match l3 * ip6 src db06::06 ioam-decap anycast * */ #include #include #include #include #include #include #include #include #include typedef struct { u32 next_index; u32 flow_label; } cache_ts_trace_t; /* packet trace format function */ static u8 * format_cache_ts_trace (u8 * s, va_list * args) { CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); cache_ts_trace_t *t = va_arg (*args, cache_ts_trace_t *); s = format (s, "CACHE: flow_label %d, next index %d", t->flow_label, t->next_index); return s; } #define foreach_cache_ts_error \ _(RECORDED, "ip6 iOAM headers cached") typedef enum { #define _(sym,str) CACHE_TS_ERROR_##sym, foreach_cache_ts_error #undef _ CACHE_TS_N_ERROR, } cache_ts_error_t; static char *cache_ts_error_strings[] = { #define _(sym,string) string, foreach_cache_ts_error #undef _ }; typedef enum { IOAM_CACHE_TS_NEXT_POP_HBYH, IOAM_CACHE_TS_ERROR_NEXT_DROP, IOAM_CACHE_TS_N_NEXT, } cache_ts_next_t; static uword ip6_ioam_cache_ts_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { ioam_cache_main_t *cm = &ioam_cache_main; u32 n_left_from, *from, *to_next; cache_ts_next_t next_index; u32 recorded = 0; from = vlib_frame_vector_args (frame); n_left_from = frame->n_vectors; next_index = node->cached_next_index; while (n_left_from > 0) { u32 n_left_to_next; vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); // TODO: dual loop while (n_left_from > 0 && n_left_to_next > 0) { u32 bi0; vlib_buffer_t *p0; u32 next0 = IOAM_CACHE_TS_NEXT_POP_HBYH; ip6_header_t *ip0; ip6_hop_by_hop_header_t *hbh0, *hbh_cmp; tcp_header_t *tcp0; u32 tcp_offset0; u32 cache_ts_index = 0; u8 cache_thread_id = 0; int result = 0; int skip = 0; bi0 = from[0]; from += 1; n_left_from -= 1; p0 = vlib_get_buffer (vm, bi0); ip0 = vlib_buffer_get_current (p0); if (IP_PROTOCOL_TCP == ip6_locate_header (p0, ip0, IP_PROTOCOL_TCP, &tcp_offset0)) { tcp0 = (tcp_header_t *) ((u8 *) ip0 + tcp_offset0); if ((tcp0->flags & TCP_FLAG_SYN) == TCP_FLAG_SYN && (tcp0->flags & TCP_FLAG_ACK) == TCP_FLAG_ACK) { /* Look up and compare */ hbh0 = (ip6_hop_by_hop_header_t *) (ip0 + 1); if (0 == ioam_cache_ts_lookup (ip0, hbh0->protocol, clib_net_to_host_u16 (tcp0->src_port), clib_net_to_host_u16 (tcp0->dst_port), clib_net_to_host_u32 (tcp0->ack_number), &hbh_cmp, &cache_ts_index, &cache_thread_id, 1)) { /* response seen */ result = -1; if (hbh_cmp) result = ip6_ioam_analyse_compare_path_delay (hbh0, hbh_cmp, cm->criteria_oneway); if (result >= 0) { /* current syn/ack is worse than the earlier: Drop */ next0 = IOAM_CACHE_TS_ERROR_NEXT_DROP; /* Check if all responses are received or time has exceeded send cached response if yes */ ioam_cache_ts_check_and_send (cache_thread_id, cache_ts_index); } else { /* Update cache with this buffer */ /* If successfully updated then skip sending it */ if (0 == (result = ioam_cache_ts_update (cache_thread_id, cache_ts_index, bi0, hbh0))) { skip = 1; } else next0 = IOAM_CACHE_TS_ERROR_NEXT_DROP; } } else { next0 = IOAM_CACHE_TS_ERROR_NEXT_DROP; } } else if ((tcp0->flags & TCP_FLAG_RST) == TCP_FLAG_RST) { /* Look up and compare */ hbh0 = (ip6_hop_by_hop_header_t *) (ip0 + 1); if (0 == ioam_cache_ts_lookup (ip0, hbh0->protocol, clib_net_to_host_u16 (tcp0->src_port), clib_net_to_host_u16 (tcp0->dst_port), clib_net_to_host_u32 (tcp0->ack_number), &hbh_cmp, &cache_ts_index, &cache_thread_id, 1)) //response seen { next0 = IOAM_CACHE_TS_ERROR_NEXT_DROP; if (hbh_cmp) ioam_cache_ts_check_and_send (cache_thread_id, cache_ts_index); } } } if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE))) { if (p0->flags & VLIB_BUFFER_IS_TRACED) { cache_ts_trace_t *t = vlib_add_trace (vm, node, p0, sizeof (*t)); t->flow_label = clib_net_to_host_u32 (ip0->ip_version_traffic_class_and_flow_label); t->next_index = next0; } } /* verify speculative enqueue, maybe switch current next frame */ if (!skip) { to_next[0] = bi0; to_next += 1; n_left_to_next -= 1; vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, n_left_to_next, bi0, next0); } } vlib_put_next_frame (vm, node, next_index, n_left_to_next); } vlib_node_increment_counter (vm, ioam_cache_ts_node.index, CACHE_TS_ERROR_RECORDED, recorded); return frame->n_vectors; } /* * Node for IP6 iOAM header cache */ /* *INDENT-OFF* */ VLIB_REGISTER_NODE (ioam_cache_ts_node) = { .function = ip6_ioam_cache_ts_node_fn, .name = "ip6-ioam-tunnel-select", .vector_size = sizeof (u32), .format_trace = format_cache_ts_trace, .type = VLIB_NODE_TYPE_INTERNAL, .n_errors = ARRAY_LEN (cache_ts_error_strings), .error_strings = cache_ts_error_strings, .n_next_nodes = IOAM_CACHE_TS_N_NEXT, /* edit / add dispositions here */ .next_nodes = { [IOAM_CACHE_TS_NEXT_POP_HBYH] = "ip6-pop-hop-by-hop", [IOAM_CACHE_TS_ERROR_NEXT_DROP] = "error-drop", }, }; /* *INDENT-ON* */ typedef struct { u32 next_index; } ip6_reset_ts_hbh_trace_t; /* packet trace format function */ static u8 * format_ip6_reset_ts_hbh_trace (u8 * s, va_list * args) { CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); ip6_reset_ts_hbh_trace_t *t = va_arg (*args, ip6_reset_ts_hbh_trace_t *); s = format (s, "IP6_IOAM_RESET_TUNNEL_SELECT_HBH: next index %d", t->next_index); return s; } #define foreach_ip6_reset_ts_hbh_error \ _(PROCESSED, "iOAM Syn/Ack Pkts processed") \ _(SAVED, "iOAM Syn Pkts state saved") \ _(REMOVED, "iOAM Syn/Ack Pkts state removed") typedef enum { #define _(sym,str) IP6_RESET_TS_HBH_ERROR_##sym, foreach_ip6_reset_ts_hbh_error #undef _ IP6_RESET_TS_HBH_N_ERROR, } ip6_reset_ts_hbh_error_t; static char *ip6_reset_ts_hbh_error_strings[] = { #define _(sym,string) string, foreach_ip6_reset_ts_hbh_error #undef _ }; #define foreach_ip6_ioam_cache_ts_input_next \ _(IP6_LOOKUP, "ip6-lookup") \ _(DROP, "error-drop") typedef enum { #define _(s,n) IP6_IOAM_CACHE_TS_INPUT_NEXT_##s, foreach_ip6_ioam_cache_ts_input_next #undef _ IP6_IOAM_CACHE_TS_INPUT_N_NEXT, } ip6_ioam_cache_ts_input_next_t; VLIB_NODE_FN (ip6_reset_ts_hbh_node) (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { ioam_cache_main_t *cm = &ioam_cache_main; u32 n_left_from, *from, *to_next; ip_lookup_next_t next_index; u32 processed = 0, cache_ts_added = 0; u64 now; u8 *rewrite = cm->rewrite; u32 rewrite_length = vec_len (rewrite); ioam_e2e_cache_option_t *e2e = 0; u8 no_of_responses = cm->wait_for_responses; from = vlib_frame_vector_args (frame); n_left_from = frame->n_vectors; next_index = node->cached_next_index; while (n_left_from > 0) { u32 n_left_to_next; vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); now = vlib_time_now (vm); while (n_left_from >= 4 && n_left_to_next >= 2) { u32 bi0, bi1; vlib_buffer_t *b0, *b1; u32 next0, next1; ip6_header_t *ip0, *ip1; tcp_header_t *tcp0, *tcp1; u32 tcp_offset0, tcp_offset1; ip6_hop_by_hop_header_t *hbh0, *hbh1; u64 *copy_src0, *copy_dst0, *copy_src1, *copy_dst1; u16 new_l0, new_l1; u32 pool_index0 = 0, pool_index1 = 0; next0 = next1 = IP6_IOAM_CACHE_TS_INPUT_NEXT_IP6_LOOKUP; /* Prefetch next iteration. */ { vlib_buffer_t *p2, *p3; p2 = vlib_get_buffer (vm, from[2]); p3 = vlib_get_buffer (vm, from[3]); vlib_prefetch_buffer_header (p2, LOAD); vlib_prefetch_buffer_header (p3, LOAD); CLIB_PREFETCH (p2->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD); CLIB_PREFETCH (p3->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD); } /* speculatively enqueue b0 to the current next frame */ to_next[0] = bi0 = from[0]; to_next[1] = bi1 = from[1]; from += 2; to_next += 2; n_left_from -= 2; n_left_to_next -= 2; b0 = vlib_get_buffer (vm, bi0); b1 = vlib_get_buffer (vm, bi1); ip0 = vlib_buffer_get_current (b0); ip1 = vlib_buffer_get_current (b1); if (IP_PROTOCOL_TCP != ip6_locate_header (b0, ip0, IP_PROTOCOL_TCP, &tcp_offset0)) { goto NEXT00; } tcp0 = (tcp_header_t *) ((u8 *) ip0 + tcp_offset0); if ((tcp0->flags & TCP_FLAG_SYN) == TCP_FLAG_SYN && (tcp0->flags & TCP_FLAG_ACK) == 0) { if (no_of_responses > 0) { /* Create TS select entry */ if (0 == ioam_cache_ts_add (ip0, clib_net_to_host_u16 (tcp0->src_port), clib_net_to_host_u16 (tcp0->dst_port), clib_net_to_host_u32 (tcp0->seq_number) + 1, no_of_responses, now, vm->thread_index, &pool_index0)) { cache_ts_added++; } } copy_dst0 = (u64 *) (((u8 *) ip0) - rewrite_length); copy_src0 = (u64 *) ip0; copy_dst0[0] = copy_src0[0]; copy_dst0[1] = copy_src0[1]; copy_dst0[2] = copy_src0[2]; copy_dst0[3] = copy_src0[3]; copy_dst0[4] = copy_src0[4]; vlib_buffer_advance (b0, -(word) rewrite_length); ip0 = vlib_buffer_get_current (b0); hbh0 = (ip6_hop_by_hop_header_t *) (ip0 + 1); /* $$$ tune, rewrite_length is a multiple of 8 */ clib_memcpy_fast (hbh0, rewrite, rewrite_length); e2e = (ioam_e2e_cache_option_t *) ((u8 *) hbh0 + cm->rewrite_pool_index_offset); e2e->pool_id = (u8) vm->thread_index; e2e->pool_index = pool_index0; ioam_e2e_id_rewrite_handler ((ioam_e2e_id_option_t *) ((u8 *) e2e + sizeof (ioam_e2e_cache_option_t)), &cm->sr_localsid_ts); /* Patch the protocol chain, insert the h-b-h (type 0) header */ hbh0->protocol = ip0->protocol; ip0->protocol = 0; new_l0 = clib_net_to_host_u16 (ip0->payload_length) + rewrite_length; ip0->payload_length = clib_host_to_net_u16 (new_l0); processed++; } NEXT00: if (IP_PROTOCOL_TCP != ip6_locate_header (b1, ip1, IP_PROTOCOL_TCP, &tcp_offset1)) { goto TRACE00; } tcp1 = (tcp_header_t *) ((u8 *) ip1 + tcp_offset1); if ((tcp1->flags & TCP_FLAG_SYN) == TCP_FLAG_SYN && (tcp1->flags & TCP_FLAG_ACK) == 0) { if (no_of_responses > 0) { /* Create TS select entry */ if (0 == ioam_cache_ts_add (ip1, clib_net_to_host_u16 (tcp1->src_port), clib_net_to_host_u16 (tcp1->dst_port), clib_net_to_host_u32 (tcp1->seq_number) + 1, no_of_responses, now, vm->thread_index, &pool_index1)) { cache_ts_added++; } } copy_dst1 = (u64 *) (((u8 *) ip1) - rewrite_length); copy_src1 = (u64 *) ip1; copy_dst1[0] = copy_src1[0]; copy_dst1[1] = copy_src1[1]; copy_dst1[2] = copy_src1[2]; copy_dst1[3] = copy_src1[3]; copy_dst1[4] = copy_src1[4]; vlib_buffer_advance (b1, -(word) rewrite_length); ip1 = vlib_buffer_get_current (b1); hbh1 = (ip6_hop_by_hop_header_t *) (ip1 + 1); /* $$$ tune, rewrite_length is a multiple of 8 */ clib_memcpy_fast (hbh1, rewrite, rewrite_length); e2e = (ioam_e2e_cache_option_t *) ((u8 *) hbh1 + cm->rewrite_pool_index_offset); e2e->pool_id = (u8) vm->thread_index; e2e->pool_index = pool_index1; ioam_e2e_id_rewrite_handler ((ioam_e2e_id_option_t *) ((u8 *) e2e + sizeof (ioam_e2e_cache_option_t)), &cm->sr_localsid_ts); /* Patch the protocol chain, insert the h-b-h (type 0) header */ hbh1->protocol = ip1->protocol; ip1->protocol = 0; new_l1 = clib_net_to_host_u16 (ip1->payload_length) + rewrite_length; ip1->payload_length = clib_host_to_net_u16 (new_l1); processed++; } TRACE00: if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE))) { if (b0->flags & VLIB_BUFFER_IS_TRACED) { ip6_reset_ts_hbh_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t)); t->next_index = next0; } if (b1->flags & VLIB_BUFFER_IS_TRACED) { ip6_reset_ts_hbh_trace_t *t = vlib_add_trace (vm, node, b1, sizeof (*t)); t->next_index = next1; } } /* verify speculative enqueue, maybe switch current next frame */ vlib_validate_buffer_enqueue_x2 (vm, node, next_index, to_next, n_left_to_next, bi0, bi1, next0, next1); } while (n_left_from > 0 && n_left_to_next > 0) { u32 bi0; vlib_buffer_t *b0; u32 next0; ip6_header_t *ip0; tcp_header_t *tcp0; u32 tcp_offset0; ip6_hop_by_hop_header_t *hbh0; u64 *copy_src0, *copy_dst0; u16 new_l0; u32 pool_index0 = 0; next0 = IP6_IOAM_CACHE_TS_INPUT_NEXT_IP6_LOOKUP; /* speculatively enqueue b0 to the current next frame */ bi0 = from[0]; to_next[0] = bi0; from += 1; to_next += 1; n_left_from -= 1; n_left_to_next -= 1; b0 = vlib_get_buffer (vm, bi0); ip0 = vlib_buffer_get_current (b0); if (IP_PROTOCOL_TCP != ip6_locate_header (b0, ip0, IP_PROTOCOL_TCP, &tcp_offset0)) { goto TRACE0; } tcp0 = (tcp_header_t *) ((u8 *) ip0 + tcp_offset0); if ((tcp0->flags & TCP_FLAG_SYN) == TCP_FLAG_SYN && (tcp0->flags & TCP_FLAG_ACK) == 0) { if (no_of_responses > 0) { /* Create TS select entry */ if (0 == ioam_cache_ts_add (ip0, clib_net_to_host_u16 (tcp0->src_port), clib_net_to_host_u16 (tcp0->dst_port), clib_net_to_host_u32 (tcp0->seq_number) + 1, no_of_responses, now, vm->thread_index, &pool_index0)) { cache_ts_added++; } } copy_dst0 = (u64 *) (((u8 *) ip0) - rewrite_length); copy_src0 = (u64 *) ip0; copy_dst0[0] = copy_src0[0]; copy_dst0[1] = copy_src0[1]; copy_dst0[2] = copy_src0[2]; copy_dst0[3] = copy_src0[3]; copy_dst0[4] = copy_src0[4]; vlib_buffer_advance (b0, -(word) rewrite_length); ip0 = vlib_buffer_get_current (b0); hbh0 = (ip6_hop_by_hop_header_t *) (ip0 + 1); /* $$$ tune, rewrite_length is a multiple of 8 */ clib_memcpy_fast (hbh0, rewrite, rewrite_length); e2e = (ioam_e2e_cache_option_t *) ((u8 *) hbh0 + cm->rewrite_pool_index_offset); e2e->pool_id = (u8) vm->thread_index; e2e->pool_index = pool_index0; ioam_e2e_id_rewrite_handler ((ioam_e2e_id_option_t *) ((u8 *) e2e + sizeof (ioam_e2e_cache_option_t)), &cm->sr_localsid_ts); /* Patch the protocol chain, insert the h-b-h (type 0) header */ hbh0->protocol = ip0->protocol; ip0->protocol = 0; new_l0 = clib_net_to_host_u16 (ip0->payload_length) + rewrite_length; ip0->payload_length = clib_host_to_net_u16 (new_l0); processed++; } TRACE0: if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) && (b0->flags & VLIB_BUFFER_IS_TRACED))) { ip6_reset_ts_hbh_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t)); t->next_index = next0; } /* verify speculative enqueue, maybe switch current next frame */ vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, n_left_to_next, bi0, next0); } vlib_put_next_frame (vm, node, next_index, n_left_to_next); } vlib_node_increment_counter (vm, cm->ip6_reset_ts_hbh_node_index, IP6_RESET_TS_HBH_ERROR_PROCESSED, processed); vlib_node_increment_counter (vm, cm->ip6_reset_ts_hbh_node_index, IP6_RESET_TS_HBH_ERROR_SAVED, cache_ts_added); return frame->n_vectors; } /* *INDENT-OFF* */ VLIB_REGISTER_NODE (ip6_reset_ts_hbh_node) = { .name = "ip6-add-syn-hop-by-hop", .vector_size = sizeof (u32), .format_trace = format_ip6_reset_ts_hbh_trace, .type = VLIB_NODE_TYPE_INTERNAL, .n_errors = ARRAY_LEN (ip6_reset_ts_hbh_error_strings), .error_strings = ip6_reset_ts_hbh_error_strings, /* See ip/lookup.h */ .n_next_nodes = IP6_IOAM_CACHE_TS_INPUT_N_NEXT, .next_nodes = { #define _(s,n) [IP6_IOAM_CACHE_TS_INPUT_NEXT_##s] = n, foreach_ip6_ioam_cache_ts_input_next #undef _ }, }; /* *INDENT-ON* */ #ifndef CLIB_MARCH_VARIANT vlib_node_registration_t ioam_cache_ts_timer_tick_node; #endif /* CLIB_MARCH_VARIANT */ typedef struct { u32 thread_index; } ioam_cache_ts_timer_tick_trace_t; /* packet trace format function */ static u8 * format_ioam_cache_ts_timer_tick_trace (u8 * s, va_list * args) { CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); ioam_cache_ts_timer_tick_trace_t *t = va_arg (*args, ioam_cache_ts_timer_tick_trace_t *); s = format (s, "IOAM_CACHE_TS_TIMER_TICK: thread index %d", t->thread_index); return s; } #define foreach_ioam_cache_ts_timer_tick_error \ _(TIMER, "Timer events") typedef enum { #define _(sym,str) IOAM_CACHE_TS_TIMER_TICK_ERROR_##sym, foreach_ioam_cache_ts_timer_tick_error #undef _ IOAM_CACHE_TS_TIMER_TICK_N_ERROR, } ioam_cache_ts_timer_tick_error_t; static char *ioam_cache_ts_timer_tick_error_strings[] = { #define _(sym,string) string, foreach_ioam_cache_ts_timer_tick_error #undef _ }; #ifndef CLIB_MARCH_VARIANT void ioam_cache_ts_timer_node_enable (vlib_main_t * vm, u8 enable) { vlib_node_set_state (vm, ioam_cache_ts_timer_tick_node.index, enable == 0 ? VLIB_NODE_STATE_DISABLED : VLIB_NODE_STATE_POLLING); } void expired_cache_ts_timer_callback (u32 * expired_timers) { ioam_cache_main_t *cm = &ioam_cache_main; int i; u32 pool_index; u32 thread_index = vlib_get_thread_index (); u32 count = 0; for (i = 0; i < vec_len (expired_timers); i++) { /* Get pool index and pool id */ pool_index = expired_timers[i] & 0x0FFFFFFF; /* Handle expiration */ ioam_cache_ts_send (thread_index, pool_index); count++; } vlib_node_increment_counter (cm->vlib_main, ioam_cache_ts_timer_tick_node.index, IOAM_CACHE_TS_TIMER_TICK_ERROR_TIMER, count); } #endif /* CLIB_MARCH_VARIANT */ static uword ioam_cache_ts_timer_tick_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * f) { ioam_cache_main_t *cm = &ioam_cache_main; u32 my_thread_index = vlib_get_thread_index (); struct timespec ts, tsrem; tw_timer_expire_timers_16t_2w_512sl (&cm->timer_wheels[my_thread_index], vlib_time_now (vm)); ts.tv_sec = 0; ts.tv_nsec = 1000 * 1000 * IOAM_CACHE_TS_TICK; while (nanosleep (&ts, &tsrem) < 0) { ts = tsrem; } return 0; } /* *INDENT-OFF* */ VLIB_REGISTER_NODE (ioam_cache_ts_timer_tick_node) = { .function = ioam_cache_ts_timer_tick_node_fn, .name = "ioam-cache-ts-timer-tick", .format_trace = format_ioam_cache_ts_timer_tick_trace, .type = VLIB_NODE_TYPE_INPUT, .n_errors = ARRAY_LEN(ioam_cache_ts_timer_tick_error_strings), .error_strings = ioam_cache_ts_timer_tick_error_strings, .n_next_nodes = 1, .state = VLIB_NODE_STATE_DISABLED, /* edit / add dispositions here */ .next_nodes = { [0] = "error-drop", }, }; /* *INDENT-ON* */ /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */