/* * Copyright (c) 2017-2019 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. */ /** * @file * @brief Session and session manager */ #include #include #include #include session_main_t session_main; static inline int session_send_evt_to_thread (void *data, void *args, u32 thread_index, session_evt_type_t evt_type) { session_event_t *evt; svm_msg_q_msg_t msg; svm_msg_q_t *mq; mq = session_main_get_vpp_event_queue (thread_index); if (PREDICT_FALSE (svm_msg_q_lock (mq))) return -1; if (PREDICT_FALSE (svm_msg_q_is_full (mq) || svm_msg_q_ring_is_full (mq, SESSION_MQ_IO_EVT_RING))) { svm_msg_q_unlock (mq); return -2; } switch (evt_type) { case SESSION_CTRL_EVT_RPC: msg = svm_msg_q_alloc_msg_w_ring (mq, SESSION_MQ_IO_EVT_RING); evt = (session_event_t *) svm_msg_q_msg_data (mq, &msg); evt->rpc_args.fp = data; evt->rpc_args.arg = args; break; case SESSION_IO_EVT_RX: case SESSION_IO_EVT_TX: case SESSION_IO_EVT_TX_FLUSH: case SESSION_IO_EVT_BUILTIN_RX: msg = svm_msg_q_alloc_msg_w_ring (mq, SESSION_MQ_IO_EVT_RING); evt = (session_event_t *) svm_msg_q_msg_data (mq, &msg); evt->session_index = *(u32 *) data; break; case SESSION_IO_EVT_BUILTIN_TX: case SESSION_CTRL_EVT_CLOSE: case SESSION_CTRL_EVT_RESET: msg = svm_msg_q_alloc_msg_w_ring (mq, SESSION_MQ_IO_EVT_RING); evt = (session_event_t *) svm_msg_q_msg_data (mq, &msg); evt->session_handle = session_handle ((session_t *) data); break; default: clib_warning ("evt unhandled!"); svm_msg_q_unlock (mq); return -1; } evt->event_type = evt_type; svm_msg_q_add_and_unlock (mq, &msg); return 0; } int session_send_io_evt_to_thread (svm_fifo_t * f, session_evt_type_t evt_type) { return session_send_evt_to_thread (&f->shr->master_session_index, 0, f->master_thread_index, evt_type); } int session_send_io_evt_to_thread_custom (void *data, u32 thread_index, session_evt_type_t evt_type) { return session_send_evt_to_thread (data, 0, thread_index, evt_type); } int session_send_ctrl_evt_to_thread (session_t * s, session_evt_type_t evt_type) { /* only events supported are disconnect and reset */ ASSERT (evt_type == SESSION_CTRL_EVT_CLOSE || evt_type == SESSION_CTRL_EVT_RESET); return session_send_evt_to_thread (s, 0, s->thread_index, evt_type); } void session_send_rpc_evt_to_thread_force (u32 thread_index, void *fp, void *rpc_args) { session_send_evt_to_thread (fp, rpc_args, thread_index, SESSION_CTRL_EVT_RPC); } void session_send_rpc_evt_to_thread (u32 thread_index, void *fp, void *rpc_args) { if (thread_index != vlib_get_thread_index ()) session_send_rpc_evt_to_thread_force (thread_index, fp, rpc_args); else { void (*fnp) (void *) = fp; fnp (rpc_args); } } void session_add_self_custom_tx_evt (transport_connection_t * tc, u8 has_prio) { session_t *s; s = session_get (tc->s_index, tc->thread_index); ASSERT (s->thread_index == vlib_get_thread_index ()); ASSERT (s->session_state != SESSION_STATE_TRANSPORT_DELETED); if (!(s->flags & SESSION_F_CUSTOM_TX)) { s->flags |= SESSION_F_CUSTOM_TX; if (svm_fifo_set_event (s->tx_fifo) || transport_connection_is_descheduled (tc)) { session_worker_t *wrk; session_evt_elt_t *elt; wrk = session_main_get_worker (tc->thread_index); if (has_prio) elt = session_evt_alloc_new (wrk); else elt = session_evt_alloc_old (wrk); elt->evt.session_index = tc->s_index; elt->evt.event_type = SESSION_IO_EVT_TX; tc->flags &= ~TRANSPORT_CONNECTION_F_DESCHED; } } } void sesssion_reschedule_tx (transport_connection_t * tc) { session_worker_t *wrk = session_main_get_worker (tc->thread_index); session_evt_elt_t *elt; ASSERT (tc->thread_index == vlib_get_thread_index ()); elt = session_evt_alloc_new (wrk); elt->evt.session_index = tc->s_index; elt->evt.event_type = SESSION_IO_EVT_TX; } static void session_program_transport_ctrl_evt (session_t * s, session_evt_type_t evt) { u32 thread_index = vlib_get_thread_index (); session_evt_elt_t *elt; session_worker_t *wrk; /* If we are in the handler thread, or being called with the worker barrier * held, just append a new event to pending disconnects vector. */ if (vlib_thread_is_main_w_barrier () || thread_index == s->thread_index) { wrk = session_main_get_worker (s->thread_index); elt = session_evt_alloc_ctrl (wrk); clib_memset (&elt->evt, 0, sizeof (session_event_t)); elt->evt.session_handle = session_handle (s); elt->evt.event_type = evt; } else session_send_ctrl_evt_to_thread (s, evt); } session_t * session_alloc (u32 thread_index) { session_worker_t *wrk = &session_main.wrk[thread_index]; session_t *s; u8 will_expand = 0; pool_get_aligned_will_expand (wrk->sessions, will_expand, CLIB_CACHE_LINE_BYTES); /* If we have peekers, let them finish */ if (PREDICT_FALSE (will_expand && vlib_num_workers ())) { clib_rwlock_writer_lock (&wrk->peekers_rw_locks); pool_get_aligned (wrk->sessions, s, CLIB_CACHE_LINE_BYTES); clib_rwlock_writer_unlock (&wrk->peekers_rw_locks); } else { pool_get_aligned (wrk->sessions, s, CLIB_CACHE_LINE_BYTES); } clib_memset (s, 0, sizeof (*s)); s->session_index = s - wrk->sessions; s->thread_index = thread_index; s->app_index = APP_INVALID_INDEX; return s; } void session_free (session_t * s) { if (CLIB_DEBUG) { u8 thread_index = s->thread_index; clib_memset (s, 0xFA, sizeof (*s)); pool_put (session_main.wrk[thread_index].sessions, s); return; } SESSION_EVT (SESSION_EVT_FREE, s); pool_put (session_main.wrk[s->thread_index].sessions, s); } u8 session_is_valid (u32 si, u8 thread_index) { session_t *s; transport_connection_t *tc; s = pool_elt_at_index (session_main.wrk[thread_index].sessions, si); if (s->thread_index != thread_index || s->session_index != si) return 0; if (s->session_state == SESSION_STATE_TRANSPORT_DELETED || s->session_state <= SESSION_STATE_LISTENING) return 1; tc = session_get_transport (s); if (s->connection_index != tc->c_index || s->thread_index != tc->thread_index || tc->s_index != si) return 0; return 1; } static void session_cleanup_notify (session_t * s, session_cleanup_ntf_t ntf) { app_worker_t *app_wrk; app_wrk = app_worker_get_if_valid (s->app_wrk_index); if (!app_wrk) return; app_worker_cleanup_notify (app_wrk, s, ntf); } void session_free_w_fifos (session_t * s) { session_cleanup_notify (s, SESSION_CLEANUP_SESSION); segment_manager_dealloc_fifos (s->rx_fifo, s->tx_fifo); session_free (s); } /** * Cleans up session and lookup table. * * Transport connection must still be valid. */ static void session_delete (session_t * s) { int rv; /* Delete from the main lookup table. */ if ((rv = session_lookup_del_session (s))) clib_warning ("session %u hash delete rv %d", s->session_index, rv); session_free_w_fifos (s); } void session_cleanup_half_open (transport_proto_t tp, session_handle_t ho_handle) { transport_cleanup_half_open (tp, session_handle_index (ho_handle)); } void session_half_open_delete_notify (transport_proto_t tp, session_handle_t ho_handle) { app_worker_t *app_wrk = app_worker_get (session_handle_data (ho_handle)); app_worker_del_half_open (app_wrk, tp, ho_handle); } session_t * session_alloc_for_connection (transport_connection_t * tc) { session_t *s; u32 thread_index = tc->thread_index; ASSERT (thread_index == vlib_get_thread_index () || transport_protocol_is_cl (tc->proto)); s = session_alloc (thread_index); s->session_type = session_type_from_proto_and_ip (tc->proto, tc->is_ip4); s->session_state = SESSION_STATE_CLOSED; /* Attach transport to session and vice versa */ s->connection_index = tc->c_index; tc->s_index = s->session_index; return s; } /** * Discards bytes from buffer chain * * It discards n_bytes_to_drop starting at first buffer after chain_b */ always_inline void session_enqueue_discard_chain_bytes (vlib_main_t * vm, vlib_buffer_t * b, vlib_buffer_t ** chain_b, u32 n_bytes_to_drop) { vlib_buffer_t *next = *chain_b; u32 to_drop = n_bytes_to_drop; ASSERT (b->flags & VLIB_BUFFER_NEXT_PRESENT); while (to_drop && (next->flags & VLIB_BUFFER_NEXT_PRESENT)) { next = vlib_get_buffer (vm, next->next_buffer); if (next->current_length > to_drop) { vlib_buffer_advance (next, to_drop); to_drop = 0; } else { to_drop -= next->current_length; next->current_length = 0; } } *chain_b = next; if (to_drop == 0) b->total_length_not_including_first_buffer -= n_bytes_to_drop; } /** * Enqueue buffer chain tail */ always_inline int session_enqueue_chain_tail (session_t * s, vlib_buffer_t * b, u32 offset, u8 is_in_order) { vlib_buffer_t *chain_b; u32 chain_bi, len, diff; vlib_main_t *vm = vlib_get_main (); u8 *data; u32 written = 0; int rv = 0; if (is_in_order && offset) { diff = offset - b->current_length; if (diff > b->total_length_not_including_first_buffer) return 0; chain_b = b; session_enqueue_discard_chain_bytes (vm, b, &chain_b, diff); chain_bi = vlib_get_buffer_index (vm, chain_b); } else chain_bi = b->next_buffer; do { chain_b = vlib_get_buffer (vm, chain_bi); data = vlib_buffer_get_current (chain_b); len = chain_b->current_length; if (!len) continue; if (is_in_order) { rv = svm_fifo_enqueue (s->rx_fifo, len, data); if (rv == len) { written += rv; } else if (rv < len) { return (rv > 0) ? (written + rv) : written; } else if (rv > len) { written += rv; /* written more than what was left in chain */ if (written > b->total_length_not_including_first_buffer) return written; /* drop the bytes that have already been delivered */ session_enqueue_discard_chain_bytes (vm, b, &chain_b, rv - len); } } else { rv = svm_fifo_enqueue_with_offset (s->rx_fifo, offset, len, data); if (rv) { clib_warning ("failed to enqueue multi-buffer seg"); return -1; } offset += len; } } while ((chain_bi = (chain_b->flags & VLIB_BUFFER_NEXT_PRESENT) ? chain_b->next_buffer : 0)); if (is_in_order) return written; return 0; } void session_fifo_tuning (session_t * s, svm_fifo_t * f, session_ft_action_t act, u32 len) { if (s->flags & SESSION_F_CUSTOM_FIFO_TUNING) { app_worker_t *app_wrk = app_worker_get (s->app_wrk_index); app_worker_session_fifo_tuning (app_wrk, s, f, act, len); if (CLIB_ASSERT_ENABLE) { segment_manager_t *sm; sm = segment_manager_get (f->segment_manager); ASSERT (f->shr->size >= 4096); ASSERT (f->shr->size <= sm->max_fifo_size); } } } /* * Enqueue data for delivery to session peer. Does not notify peer of enqueue * event but on request can queue notification events for later delivery by * calling stream_server_flush_enqueue_events(). * * @param tc Transport connection which is to be enqueued data * @param b Buffer to be enqueued * @param offset Offset at which to start enqueueing if out-of-order * @param queue_event Flag to indicate if peer is to be notified or if event * is to be queued. The former is useful when more data is * enqueued and only one event is to be generated. * @param is_in_order Flag to indicate if data is in order * @return Number of bytes enqueued or a negative value if enqueueing failed. */ int session_enqueue_stream_connection (transport_connection_t * tc, vlib_buffer_t * b, u32 offset, u8 queue_event, u8 is_in_order) { session_t *s; int enqueued = 0, rv, in_order_off; s = session_get (tc->s_index, tc->thread_index); if (is_in_order) { enqueued = svm_fifo_enqueue (s->rx_fifo, b->current_length, vlib_buffer_get_current (b)); if (PREDICT_FALSE ((b->flags & VLIB_BUFFER_NEXT_PRESENT) && enqueued >= 0)) { in_order_off = enqueued > b->current_length ? enqueued : 0; rv = session_enqueue_chain_tail (s, b, in_order_off, 1); if (rv > 0) enqueued += rv; } } else { rv = svm_fifo_enqueue_with_offset (s->rx_fifo, offset, b->current_length, vlib_buffer_get_current (b)); if (PREDICT_FALSE ((b->flags & VLIB_BUFFER_NEXT_PRESENT) && !rv)) session_enqueue_chain_tail (s, b, offset + b->current_length, 0); /* if something was enqueued, report even this as success for ooo * segment handling */ return rv; } if (queue_event) { /* Queue RX event on this fifo. Eventually these will need to be flushed * by calling stream_server_flush_enqueue_events () */ session_worker_t *wrk; wrk = session_main_get_worker (s->thread_index); if (!(s->flags & SESSION_F_RX_EVT)) { s->flags |= SESSION_F_RX_EVT; vec_add1 (wrk->session_to_enqueue[tc->proto], s->session_index); } session_fifo_tuning (s, s->rx_fifo, SESSION_FT_ACTION_ENQUEUED, 0); } return enqueued; } int s
/*
 *------------------------------------------------------------------
 * memory_client.c - API message handling, client code.
 *
 * Copyright (c) 2010 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.
 *------------------------------------------------------------------
 */

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <string.h>
#include <vppinfra/clib.h>
#include <vppinfra/vec.h>
#include <vppinfra/hash.h>
#include <vppinfra/bitmap.h>
#include <vppinfra/fifo.h>
#include <vppinfra/time.h>
#include <vppinfra/mheap.h>
#include <vppinfra/heap.h>
#include <vppinfra/pool.h>
#include <vppinfra/format.h>

#include <vlib/vlib.h>
#include <vlib/unix/unix.h>
#include <vlibmemory/api.h>

#include <vlibmemory/vl_memory_msg_enum.h>

#define vl_typedefs		/* define message structures */
#include <vlibmemory/vl_memory_api_h.h>
#undef vl_typedefs

#define vl_endianfun		/* define message structures */
#include <vlibmemory/vl_memory_api_h.h>
#undef vl_endianfun

/* instantiate all the print functions we know about */
#define vl_print(handle, ...) clib_warning (__VA_ARGS__)
#define vl_printfun
#include <vlibmemory/vl_memory_api_h.h>
#undef vl_printfun

typedef struct
{
  u8 rx_thread_jmpbuf_valid;
  u8 connected_to_vlib;
  jmp_buf rx_thread_jmpbuf;
  pthread_t rx_thread_handle;
  /* Plugin message base lookup scheme */
  volatile u8 first_msg_id_reply_ready;
  u16 first_msg_id_reply;
} memory_client_main_t;

memory_client_main_t memory_client_main;

static void *
rx_thread_fn (void *arg)
{
  unix_shared_memory_queue_t *q;
  memory_client_main_t *mm = &memory_client_main;
  api_main_t *am = &api_main;

  q = am->vl_input_queue;

  /* So we can make the rx thread terminate cleanly */
  if (setjmp (mm->rx_thread_jmpbuf) == 0)
    {
      mm->rx_thread_jmpbuf_valid = 1;
      while (1)
	{
	  vl_msg_api_queue_handler (q);
	}
    }
  pthread_exit (0);
}

static void
vl_api_rx_thread_exit_t_handler (vl_api_rx_thread_exit_t * mp)
{
  memory_client_main_t *mm = &memory_client_main;
  vl_msg_api_free (mp);
  longjmp (mm->rx_thread_jmpbuf, 1);
}

static void
noop_handler (void *notused)
{
}

#define foreach_api_msg						\
_(RX_THREAD_EXIT, rx_thread_exit)

static int
connect_to_vlib_internal (char *svm_name, char *client_name,
			  int rx_queue_size, int want_pthread)
{
  int rv = 0;
  memory_client_main_t *mm = &memory_client_main;

  if ((rv = vl_client_api_map (svm_name)))
    {
      clib_warning ("vl_client_api map rv %d", rv);
      return rv;
    }

#define _(N,n)                                                  \
    vl_msg_api_set_handlers(VL_API_##N, #n,                     \
                            vl_api_##n##_t_handler,             \
                            noop_handler,                       \
                            vl_api_##n##_t_endian,              \
                            vl_api_##n##_t_print,               \
                            sizeof(vl_api_##n##_t), 1);
  foreach_api_msg;
#undef _

  if (vl_client_connect (client_name, 0 /* punt quota */ ,
			 rx_queue_size /* input queue */ ) < 0)
    {
      vl_client_api_unmap ();
      return -1;
    }

  /* Start the rx queue thread */

  if (want_pthread)
    {
      rv = pthread_create (&mm->rx_thread_handle,
			   NULL /*attr */ , rx_thread_fn, 0);
      if (rv)
	clib_warning ("pthread_create returned %d", rv);
    }

  mm->connected_to_vlib = 1;
  return 0;
}

int
vl_client_connect_to_vlib (char *svm_name, char *client_name,
			   int rx_queue_size)
{
  return connect_to_vlib_internal (svm_name, client_name, rx_queue_size,
				   1 /* want pthread */ );
}

int
vl_client_connect_to_vlib_no_rx_pthread (char *svm_name, char *client_name,
					 int rx_queue_size)
{
  return connect_to_vlib_internal (svm_name, client_name, rx_queue_size,
				   0 /* want pthread */ );
}

void
vl_client_disconnect_from_vlib (void)
{
  memory_client_main_t *mm = &memory_client_main;
  api_main_t *am = &api_main;
  uword junk;

  if (mm->rx_thread_jmpbuf_valid)
    {
      vl_api_rx_thread_exit_t *ep;
      ep = vl_msg_api_alloc (sizeof (*ep));
      ep->_vl_msg_id = ntohs (VL_API_RX_THREAD_EXIT);
      vl_msg_api_send_shmem (am->vl_input_queue, (u8 *) & ep);
      pthread_join (mm->rx_thread_handle, (void **) &junk);
    }
  if (mm->connected_to_vlib)
    {
      vl_client_disconnect ();
      vl_client_api_unmap ();
    }
  memset (mm, 0, sizeof (*mm));
}

static void vl_api_get_first_msg_id_reply_t_handler
  (vl_api_get_first_msg_id_reply_t * mp)
{
  memory_client_main_t *mm = &memory_client_main;
  i32 retval = ntohl (mp->retval);

  mm->first_msg_id_reply = (retval >= 0) ? ntohs (mp->first_msg_id) : ~0;
  mm->first_msg_id_reply_ready = 1;
}

u16
vl_client_get_first_plugin_msg_id (char *plugin_name)
{
  vl_api_get_first_msg_id_t *mp;
  api_main_t *am = &api_main;
  memory_client_main_t *mm = &memory_client_main;
  f64 timeout;
  void *old_handler;
  clib_time_t clib_time;
  u16 rv = ~0;

  if (strlen (plugin_name) + 1 > sizeof (mp->name))
    return (rv);

  memset (&clib_time, 0, sizeof (clib_time));
  clib_time_init (&clib_time);

  /* Push this plugin's first_msg_id_reply handler */
  old_handler = am->msg_handlers[VL_API_GET_FIRST_MSG_ID_REPLY];
  am->msg_handlers[VL_API_GET_FIRST_MSG_ID_REPLY] = (void *)
    vl_api_get_first_msg_id_reply_t_handler;

  /* Ask the data-plane for the message-ID base of the indicated plugin */
  mm->first_msg_id_reply_ready = 0;

  mp = vl_msg_api_alloc (sizeof (*mp));
  memset (mp, 0, sizeof (*mp));
  mp->_vl_msg_id = ntohs (VL_API_GET_FIRST_MSG_ID);
  mp->client_index = am->my_client_index;
  strncpy ((char *) mp->name, plugin_name, sizeof (mp->name) - 1);

  vl_msg_api_send_shmem (am->shmem_hdr->vl_input_queue, (u8 *) & mp);

  /* Synchronously wait for the answer */
  do
    {
      timeout = clib_time_now (&clib_time) + 1.0;

      while (clib_time_now (&clib_time) < timeout)
	{
	  if (mm->first_msg_id_reply_ready == 1)
	    {
	      rv = mm->first_msg_id_reply;
	      goto result;
	    }
	}
      /* Restore old handler */
      am->msg_handlers[VL_API_GET_FIRST_MSG_ID_REPLY] = old_handler;

      return rv;
    }
  while (0);

result:

  /* Restore the old handler */
  am->msg_handlers[VL_API_GET_FIRST_MSG_ID_REPLY] = old_handler;

  if (rv == (u16) ~ 0)
    clib_warning ("plugin '%s' not registered", plugin_name);

  return rv;
}

void
vlib_node_sync_stats (vlib_main_t * vm, vlib_node_t * n)
{
  clib_warning ("STUB called...");
}

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */
mory %U", unformat_memory_size, &tmp)) { if (tmp >= 0x100000000) return clib_error_return (0, "memory size %llx (%lld) too large", tmp, tmp); smm->configured_v4_session_table_memory = tmp; } else if (unformat (input, "v4-halfopen-table-memory %U", unformat_memory_size, &tmp)) { if (tmp >= 0x100000000) return clib_error_return (0, "memory size %llx (%lld) too large", tmp, tmp); smm->configured_v4_halfopen_table_memory = tmp; } else if (unformat (input, "v6-session-table-memory %U", unformat_memory_size, &tmp)) { if (tmp >= 0x100000000) return clib_error_return (0, "memory size %llx (%lld) too large", tmp, tmp); smm->configured_v6_session_table_memory = tmp; } else if (unformat (input, "v6-halfopen-table-memory %U", unformat_memory_size, &tmp)) { if (tmp >= 0x100000000) return clib_error_return (0, "memory size %llx (%lld) too large", tmp, tmp); smm->configured_v6_halfopen_table_memory = tmp; } else if (unformat (input, "local-endpoints-table-memory %U", unformat_memory_size, &tmp)) { if (tmp >= 0x100000000) return clib_error_return (0, "memory size %llx (%lld) too large", tmp, tmp); smm->local_endpoints_table_memory = tmp; } else if (unformat (input, "local-endpoints-table-buckets %d", &smm->local_endpoints_table_buckets)) ; /* Deprecated but maintained for compatibility */ else if (unformat (input, "evt_qs_memfd_seg")) ; else if (unformat (input, "evt_qs_seg_size %U", unformat_memory_size, &smm->evt_qs_segment_size)) ; else if (unformat (input, "enable")) smm->session_enable_asap = 1; else if (unformat (input, "segment-baseva 0x%lx", &smm->session_baseva)) ; else if (unformat (input, "use-app-socket-api")) appns_sapi_enable (); else if (unformat (input, "poll-main")) smm->poll_main = 1; else return clib_error_return (0, "unknown input `%U'", format_unformat_error, input); } return 0; } VLIB_CONFIG_FUNCTION (session_config_fn, "session"); /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */