+ return VPPCOM_OK;
+}
+
+static int
+vppcom_session_disconnect (u32 session_handle)
+{
+ vcl_worker_t *wrk = vcl_worker_get_current ();
+ vcl_session_t *session, *listen_session;
+ vcl_session_state_t state;
+ u64 vpp_handle;
+
+ session = vcl_session_get_w_handle (wrk, session_handle);
+ if (!session)
+ return VPPCOM_EBADFD;
+
+ vpp_handle = session->vpp_handle;
+ state = session->session_state;
+
+ VDBG (1, "session %u [0x%llx] state 0x%x (%s)", session->session_index,
+ vpp_handle, state, vppcom_session_state_str (state));
+
+ if (PREDICT_FALSE (state == VCL_STATE_LISTEN))
+ {
+ VDBG (0, "ERROR: Cannot disconnect a listen socket!");
+ return VPPCOM_EBADFD;
+ }
+
+ if (state == VCL_STATE_VPP_CLOSING)
+ {
+ vcl_send_session_disconnected_reply (wrk, session, 0);
+ VDBG (1, "session %u [0x%llx]: sending disconnect REPLY...",
+ session->session_index, vpp_handle);
+ }
+ else
+ {
+ /* Session doesn't have an event queue yet. Probably a non-blocking
+ * connect. Wait for the reply */
+ if (PREDICT_FALSE (!session->vpp_evt_q))
+ return VPPCOM_OK;
+
+ VDBG (1, "session %u [0x%llx]: sending disconnect...",
+ session->session_index, vpp_handle);
+ vcl_send_session_disconnect (wrk, session);
+ }
+
+ if (session->listener_index != VCL_INVALID_SESSION_INDEX)
+ {
+ listen_session = vcl_session_get (wrk, session->listener_index);
+ listen_session->n_accepted_sessions--;
+ }
+
+ return VPPCOM_OK;
+}
+
+static void
+vcl_session_cleanup_handler (vcl_worker_t * wrk, void *data)
+{
+ session_cleanup_msg_t *msg;
+ vcl_session_t *session;
+
+ msg = (session_cleanup_msg_t *) data;
+ session = vcl_session_get_w_vpp_handle (wrk, msg->handle);
+ if (!session)
+ {
+ VDBG (0, "disconnect confirmed for unknown handle 0x%llx", msg->handle);
+ return;
+ }
+
+ if (msg->type == SESSION_CLEANUP_TRANSPORT)
+ {
+ /* Transport was cleaned up before we confirmed close. Probably the
+ * app is still waiting for some data that cannot be delivered.
+ * Confirm close to make sure everything is cleaned up.
+ * Move to undetermined state to ensure that the session is not
+ * removed before both vpp and the app cleanup.
+ * - If the app closes first, the session is moved to CLOSED state
+ * and the session cleanup notification from vpp removes the
+ * session.
+ * - If vpp cleans up the session first, the session is moved to
+ * DETACHED state lower and subsequently the close from the app
+ * frees the session
+ */
+ if (session->session_state == VCL_STATE_VPP_CLOSING)
+ {
+ vppcom_session_disconnect (vcl_session_handle (session));
+ session->session_state = VCL_STATE_UPDATED;
+ }
+ else if (session->session_state == VCL_STATE_DISCONNECT)
+ {
+ vcl_send_session_reset_reply (wrk, session, 0);
+ session->session_state = VCL_STATE_UPDATED;
+ }
+ return;
+ }
+
+ vcl_session_table_del_vpp_handle (wrk, msg->handle);
+ /* Should not happen. App did not close the connection so don't free it. */
+ if (session->session_state != VCL_STATE_CLOSED)
+ {
+ VDBG (0, "app did not close session %d", session->session_index);
+ session->session_state = VCL_STATE_DETACHED;
+ session->vpp_handle = VCL_INVALID_SESSION_HANDLE;
+ return;
+ }
+ vcl_session_free (wrk, session);
+}
+
+static void
+vcl_session_req_worker_update_handler (vcl_worker_t * wrk, void *data)
+{
+ session_req_worker_update_msg_t *msg;
+ vcl_session_t *s;
+
+ msg = (session_req_worker_update_msg_t *) data;
+ s = vcl_session_get_w_vpp_handle (wrk, msg->session_handle);
+ if (!s)
+ return;
+
+ vec_add1 (wrk->pending_session_wrk_updates, s->session_index);
+}
+
+static void
+vcl_session_worker_update_reply_handler (vcl_worker_t * wrk, void *data)
+{
+ session_worker_update_reply_msg_t *msg;
+ vcl_session_t *s;
+
+ msg = (session_worker_update_reply_msg_t *) data;
+ s = vcl_session_get_w_vpp_handle (wrk, msg->handle);
+ if (!s)
+ {
+ VDBG (0, "unknown handle 0x%llx", msg->handle);
+ return;
+ }
+
+ if (s->rx_fifo)
+ {
+ if (vcl_segment_attach_session (msg->segment_handle, msg->rx_fifo,
+ msg->tx_fifo, (uword) ~0, 0, s))
+ {
+ VDBG (0, "failed to attach fifos for %u", s->session_index);
+ return;
+ }
+ }
+ s->session_state = VCL_STATE_UPDATED;
+
+ VDBG (0, "session %u[0x%llx] moved to worker %u", s->session_index,
+ s->vpp_handle, wrk->wrk_index);
+}
+
+static int
+vcl_api_recv_fd (vcl_worker_t * wrk, int *fds, int n_fds)
+{
+
+ if (vcm->cfg.vpp_app_socket_api)
+ return vcl_sapi_recv_fds (wrk, fds, n_fds);
+
+ return vcl_bapi_recv_fds (wrk, fds, n_fds);
+}
+
+static void
+vcl_session_app_add_segment_handler (vcl_worker_t * wrk, void *data)
+{
+ ssvm_segment_type_t seg_type = SSVM_SEGMENT_SHM;
+ session_app_add_segment_msg_t *msg;
+ u64 segment_handle;
+ int fd = -1;
+
+ msg = (session_app_add_segment_msg_t *) data;
+
+ if (msg->fd_flags)
+ {
+ vcl_api_recv_fd (wrk, &fd, 1);
+ seg_type = SSVM_SEGMENT_MEMFD;
+ }
+
+ segment_handle = msg->segment_handle;
+ if (segment_handle == VCL_INVALID_SEGMENT_HANDLE)
+ {
+ clib_warning ("invalid segment handle");
+ return;
+ }
+
+ if (vcl_segment_attach (segment_handle, (char *) msg->segment_name,
+ seg_type, fd))
+ {
+ VDBG (0, "vcl_segment_attach ('%s') failed", msg->segment_name);
+ return;
+ }
+
+ VDBG (1, "mapped new segment '%s' size %d", msg->segment_name,
+ msg->segment_size);
+}
+
+static void
+vcl_session_app_del_segment_handler (vcl_worker_t * wrk, void *data)
+{
+ session_app_del_segment_msg_t *msg = (session_app_del_segment_msg_t *) data;
+ vcl_segment_detach (msg->segment_handle);
+ VDBG (1, "Unmapped segment: %d", msg->segment_handle);
+}
+
+static void
+vcl_worker_rpc_handler (vcl_worker_t * wrk, void *data)
+{
+ if (!vcm->wrk_rpc_fn)
+ return;
+
+ (vcm->wrk_rpc_fn) (((session_app_wrk_rpc_msg_t *) data)->data);
+}
+
+static void
+vcl_session_transport_attr_reply_handler (vcl_worker_t *wrk, void *data)
+{
+ session_transport_attr_reply_msg_t *mp;
+
+ if (!wrk->session_attr_op)
+ return;
+
+ mp = (session_transport_attr_reply_msg_t *) data;
+
+ wrk->session_attr_op_rv = mp->retval;
+ wrk->session_attr_op = 0;
+ wrk->session_attr_rv = mp->attr;
+}
+
+static int
+vcl_handle_mq_event (vcl_worker_t * wrk, session_event_t * e)
+{
+ session_disconnected_msg_t *disconnected_msg;
+ session_connected_msg_t *connected_msg;
+ session_reset_msg_t *reset_msg;
+ session_event_t *ecpy;
+ vcl_session_t *s;
+ u32 sid;
+
+ switch (e->event_type)
+ {
+ case SESSION_IO_EVT_RX:
+ case SESSION_IO_EVT_TX:
+ s = vcl_session_get (wrk, e->session_index);
+ if (!s || !vcl_session_is_open (s))
+ break;
+ vec_add1 (wrk->unhandled_evts_vector, *e);
+ break;
+ case SESSION_CTRL_EVT_BOUND:
+ /* We can only wait for only one listen so not postponed */
+ vcl_session_bound_handler (wrk, (session_bound_msg_t *) e->data);
+ break;
+ case SESSION_CTRL_EVT_ACCEPTED:
+ s = vcl_session_accepted (wrk, (session_accepted_msg_t *) e->data);
+ if (vcl_session_has_attr (s, VCL_SESS_ATTR_NONBLOCK))
+ {
+ vec_add2 (wrk->unhandled_evts_vector, ecpy, 1);
+ *ecpy = *e;
+ ecpy->postponed = 1;
+ ecpy->session_index = s->session_index;
+ }
+ break;
+ case SESSION_CTRL_EVT_CONNECTED:
+ connected_msg = (session_connected_msg_t *) e->data;
+ sid = vcl_session_connected_handler (wrk, connected_msg);
+ if (!(s = vcl_session_get (wrk, sid)))
+ break;
+ if (vcl_session_has_attr (s, VCL_SESS_ATTR_NONBLOCK))
+ {
+ vec_add2 (wrk->unhandled_evts_vector, ecpy, 1);
+ *ecpy = *e;
+ ecpy->postponed = 1;
+ ecpy->session_index = s->session_index;
+ }
+ break;
+ case SESSION_CTRL_EVT_DISCONNECTED:
+ disconnected_msg = (session_disconnected_msg_t *) e->data;
+ if (!(s = vcl_session_get_w_vpp_handle (wrk, disconnected_msg->handle)))
+ break;
+ if (vcl_session_has_attr (s, VCL_SESS_ATTR_NONBLOCK))
+ {
+ vec_add1 (wrk->unhandled_evts_vector, *e);
+ break;
+ }
+ if (!(s = vcl_session_disconnected_handler (wrk, disconnected_msg)))
+ break;
+ VDBG (0, "disconnected session %u [0x%llx]", s->session_index,
+ s->vpp_handle);
+ break;
+ case SESSION_CTRL_EVT_RESET:
+ reset_msg = (session_reset_msg_t *) e->data;
+ if (!(s = vcl_session_get_w_vpp_handle (wrk, reset_msg->handle)))
+ break;
+ if (vcl_session_has_attr (s, VCL_SESS_ATTR_NONBLOCK))
+ {
+ vec_add1 (wrk->unhandled_evts_vector, *e);
+ break;
+ }
+ vcl_session_reset_handler (wrk, (session_reset_msg_t *) e->data);
+ break;
+ case SESSION_CTRL_EVT_UNLISTEN_REPLY:
+ vcl_session_unlisten_reply_handler (wrk, e->data);
+ break;
+ case SESSION_CTRL_EVT_MIGRATED:
+ vcl_session_migrated_handler (wrk, e->data);
+ break;
+ case SESSION_CTRL_EVT_CLEANUP:
+ vcl_session_cleanup_handler (wrk, e->data);
+ break;
+ case SESSION_CTRL_EVT_REQ_WORKER_UPDATE:
+ vcl_session_req_worker_update_handler (wrk, e->data);
+ break;
+ case SESSION_CTRL_EVT_WORKER_UPDATE_REPLY:
+ vcl_session_worker_update_reply_handler (wrk, e->data);
+ break;
+ case SESSION_CTRL_EVT_APP_ADD_SEGMENT:
+ vcl_session_app_add_segment_handler (wrk, e->data);
+ break;
+ case SESSION_CTRL_EVT_APP_DEL_SEGMENT:
+ vcl_session_app_del_segment_handler (wrk, e->data);
+ break;
+ case SESSION_CTRL_EVT_APP_WRK_RPC:
+ vcl_worker_rpc_handler (wrk, e->data);
+ break;
+ case SESSION_CTRL_EVT_TRANSPORT_ATTR_REPLY:
+ vcl_session_transport_attr_reply_handler (wrk, e->data);
+ break;
+ default:
+ clib_warning ("unhandled %u", e->event_type);
+ }
+ return VPPCOM_OK;
+}
+
+static int
+vppcom_wait_for_session_state_change (u32 session_index,
+ vcl_session_state_t state,
+ f64 wait_for_time)
+{
+ vcl_worker_t *wrk = vcl_worker_get_current ();
+ f64 timeout = clib_time_now (&wrk->clib_time) + wait_for_time;
+ vcl_session_t *volatile session;
+ svm_msg_q_msg_t msg;
+ session_event_t *e;
+
+ do
+ {
+ session = vcl_session_get (wrk, session_index);
+ if (PREDICT_FALSE (!session))
+ {
+ return VPPCOM_EBADFD;
+ }
+ if (session->session_state == state)
+ {
+ return VPPCOM_OK;
+ }
+ if (session->session_state == VCL_STATE_DETACHED)
+ {
+ return VPPCOM_ECONNREFUSED;
+ }
+
+ if (svm_msg_q_sub (wrk->app_event_queue, &msg, SVM_Q_NOWAIT, 0))
+ {
+ usleep (100);
+ continue;
+ }
+ e = svm_msg_q_msg_data (wrk->app_event_queue, &msg);
+ vcl_handle_mq_event (wrk, e);
+ svm_msg_q_free_msg (wrk->app_event_queue, &msg);
+ }
+ while (clib_time_now (&wrk->clib_time) < timeout);
+
+ VDBG (0, "timeout waiting for state 0x%x (%s)", state,
+ vppcom_session_state_str (state));
+ vcl_evt (VCL_EVT_SESSION_TIMEOUT, session, session_state);
+
+ return VPPCOM_ETIMEDOUT;
+}
+
+static void
+vcl_handle_pending_wrk_updates (vcl_worker_t * wrk)
+{
+ vcl_session_state_t state;
+ vcl_session_t *s;
+ u32 *sip;
+
+ if (PREDICT_TRUE (vec_len (wrk->pending_session_wrk_updates) == 0))
+ return;
+
+ vec_foreach (sip, wrk->pending_session_wrk_updates)
+ {
+ s = vcl_session_get (wrk, *sip);
+ vcl_send_session_worker_update (wrk, s, wrk->wrk_index);
+ state = s->session_state;
+ vppcom_wait_for_session_state_change (s->session_index, VCL_STATE_UPDATED,
+ 5);
+ s->session_state = state;
+ }
+ vec_reset_length (wrk->pending_session_wrk_updates);
+}
+
+void
+vcl_worker_flush_mq_events (vcl_worker_t *wrk)
+{
+ svm_msg_q_msg_t *msg;
+ session_event_t *e;
+ svm_msg_q_t *mq;
+ int i;
+
+ mq = wrk->app_event_queue;
+ vcl_mq_dequeue_batch (wrk, mq, ~0);
+
+ for (i = 0; i < vec_len (wrk->mq_msg_vector); i++)
+ {
+ msg = vec_elt_at_index (wrk->mq_msg_vector, i);
+ e = svm_msg_q_msg_data (mq, msg);
+ vcl_handle_mq_event (wrk, e);
+ svm_msg_q_free_msg (mq, msg);
+ }
+ vec_reset_length (wrk->mq_msg_vector);
+ vcl_handle_pending_wrk_updates (wrk);
+}
+
+void
+vcl_flush_mq_events (void)
+{
+ vcl_worker_flush_mq_events (vcl_worker_get_current ());
+}
+
+static int
+vppcom_session_unbind (u32 session_handle)
+{
+ vcl_worker_t *wrk = vcl_worker_get_current ();
+ session_accepted_msg_t *accepted_msg;
+ vcl_session_t *session = 0;
+ vcl_session_msg_t *evt;
+
+ session = vcl_session_get_w_handle (wrk, session_handle);
+ if (!session)
+ return VPPCOM_EBADFD;
+
+ /* Flush pending accept events, if any */
+ while (clib_fifo_elts (session->accept_evts_fifo))
+ {
+ clib_fifo_sub2 (session->accept_evts_fifo, evt);
+ accepted_msg = &evt->accepted_msg;
+ vcl_session_table_del_vpp_handle (wrk, accepted_msg->handle);
+ vcl_send_session_accepted_reply (session->vpp_evt_q,
+ accepted_msg->context,
+ accepted_msg->handle, -1);
+ }
+ clib_fifo_free (session->accept_evts_fifo);
+
+ vcl_send_session_unlisten (wrk, session);
+
+ VDBG (1, "session %u [0x%llx]: sending unbind!", session->session_index,
+ session->vpp_handle);
+ vcl_evt (VCL_EVT_UNBIND, session);
+
+ session->vpp_handle = ~0;
+ session->session_state = VCL_STATE_DISCONNECT;
+
+ return VPPCOM_OK;
+}
+
+/**
+ * Handle app exit
+ *
+ * Notify vpp of the disconnect and mark the worker as free. If we're the
+ * last worker, do a full cleanup otherwise, since we're probably a forked
+ * child, avoid syscalls as much as possible. We might've lost privileges.
+ */
+void
+vppcom_app_exit (void)
+{
+ if (!pool_elts (vcm->workers))
+ return;
+ vcl_worker_cleanup (vcl_worker_get_current (), 1 /* notify vpp */ );
+ vcl_set_worker_index (~0);
+ vcl_elog_stop (vcm);
+}
+
+static int
+vcl_api_attach (void)
+{
+ if (vcm->cfg.vpp_app_socket_api)
+ return vcl_sapi_attach ();
+
+ return vcl_bapi_attach ();
+}
+
+static void
+vcl_api_detach (vcl_worker_t * wrk)
+{
+ vcl_send_app_detach (wrk);
+
+ if (vcm->cfg.vpp_app_socket_api)
+ return vcl_sapi_detach (wrk);
+
+ return vcl_bapi_disconnect_from_vpp ();
+}
+
+/*
+ * VPPCOM Public API functions
+ */
+int
+vppcom_app_create (const char *app_name)
+{
+ vppcom_cfg_t *vcl_cfg = &vcm->cfg;
+ int rv;
+
+ if (vcm->is_init)
+ {
+ VDBG (1, "already initialized");
+ return VPPCOM_EEXIST;
+ }
+
+ vcm->is_init = 1;
+ vppcom_cfg (&vcm->cfg);
+ vcl_cfg = &vcm->cfg;
+
+ vcm->main_cpu = pthread_self ();
+ vcm->main_pid = getpid ();
+ vcm->app_name = format (0, "%s", app_name);
+ fifo_segment_main_init (&vcm->segment_main, vcl_cfg->segment_baseva,
+ 20 /* timeout in secs */ );
+ pool_alloc (vcm->workers, vcl_cfg->max_workers);
+ clib_spinlock_init (&vcm->workers_lock);
+ clib_rwlock_init (&vcm->segment_table_lock);
+ atexit (vppcom_app_exit);
+ vcl_elog_init (vcm);
+
+ /* Allocate default worker */
+ vcl_worker_alloc_and_init ();
+
+ if ((rv = vcl_api_attach ()))
+ return rv;
+
+ VDBG (0, "app_name '%s', my_client_index %d (0x%x)", app_name,
+ vcm->workers[0].api_client_handle, vcm->workers[0].api_client_handle);
+
+ return VPPCOM_OK;
+}
+
+void
+vppcom_app_destroy (void)
+{
+ vcl_worker_t *wrk, *current_wrk;
+ void *heap;
+
+ if (!pool_elts (vcm->workers))
+ return;
+
+ vcl_evt (VCL_EVT_DETACH, vcm);
+
+ current_wrk = vcl_worker_get_current ();
+
+ /* *INDENT-OFF* */
+ pool_foreach (wrk, vcm->workers) {
+ if (current_wrk != wrk)
+ vcl_worker_cleanup (wrk, 0 /* notify vpp */ );
+ }
+ /* *INDENT-ON* */
+
+ vcl_api_detach (current_wrk);
+ vcl_worker_cleanup (current_wrk, 0 /* notify vpp */ );
+
+ vcl_elog_stop (vcm);
+
+ /*
+ * Free the heap and fix vcm
+ */
+ heap = clib_mem_get_heap ();
+ munmap (clib_mem_get_heap_base (heap), clib_mem_get_heap_size (heap));
+
+ vcm = &_vppcom_main;
+ vcm->is_init = 0;
+}
+
+int
+vppcom_session_create (u8 proto, u8 is_nonblocking)
+{
+ vcl_worker_t *wrk = vcl_worker_get_current ();
+ vcl_session_t *session;
+
+ session = vcl_session_alloc (wrk);
+
+ session->session_type = proto;
+ session->session_state = VCL_STATE_CLOSED;
+ session->vpp_handle = ~0;
+ session->is_dgram = vcl_proto_is_dgram (proto);
+
+ if (is_nonblocking)
+ vcl_session_set_attr (session, VCL_SESS_ATTR_NONBLOCK);
+
+ vcl_evt (VCL_EVT_CREATE, session, session_type, session->session_state,
+ is_nonblocking, session_index);
+
+ VDBG (0, "created session %u", session->session_index);
+
+ return vcl_session_handle (session);
+}
+
+int
+vcl_session_cleanup (vcl_worker_t * wrk, vcl_session_t * s,
+ vcl_session_handle_t sh, u8 do_disconnect)
+{
+ int rv = VPPCOM_OK;
+
+ VDBG (1, "session %u [0x%llx] closing", s->session_index, s->vpp_handle);
+
+ if (s->flags & VCL_SESSION_F_IS_VEP)
+ {
+ u32 next_sh = s->vep.next_sh;
+ while (next_sh != ~0)
+ {
+ rv = vppcom_epoll_ctl (sh, EPOLL_CTL_DEL, next_sh, 0);
+ if (PREDICT_FALSE (rv < 0))
+ VDBG (0, "vpp handle 0x%llx, sh %u: EPOLL_CTL_DEL vep_idx %u"
+ " failed! rv %d (%s)", s->vpp_handle, next_sh,
+ s->vep.vep_sh, rv, vppcom_retval_str (rv));
+ next_sh = s->vep.next_sh;
+ }
+ goto free_session;
+ }
+
+ if (s->flags & VCL_SESSION_F_IS_VEP_SESSION)
+ {
+ rv = vppcom_epoll_ctl (s->vep.vep_sh, EPOLL_CTL_DEL, sh, 0);
+ if (rv < 0)
+ VDBG (0, "session %u [0x%llx]: EPOLL_CTL_DEL vep_idx %u "
+ "failed! rv %d (%s)", s->session_index, s->vpp_handle,
+ s->vep.vep_sh, rv, vppcom_retval_str (rv));
+ }
+
+ if (!do_disconnect)
+ {
+ VDBG (1, "session %u [0x%llx] disconnect skipped",
+ s->session_index, s->vpp_handle);
+ goto cleanup;
+ }
+
+ if (s->session_state == VCL_STATE_LISTEN)
+ {
+ rv = vppcom_session_unbind (sh);
+ if (PREDICT_FALSE (rv < 0))
+ VDBG (0, "session %u [0x%llx]: listener unbind failed! "
+ "rv %d (%s)", s->session_index, s->vpp_handle, rv,
+ vppcom_retval_str (rv));
+ return rv;
+ }
+ else if (vcl_session_is_ready (s)
+ || (vcl_session_is_connectable_listener (wrk, s)))
+ {
+ rv = vppcom_session_disconnect (sh);
+ if (PREDICT_FALSE (rv < 0))
+ VDBG (0, "ERROR: session %u [0x%llx]: disconnect failed!"
+ " rv %d (%s)", s->session_index, s->vpp_handle,
+ rv, vppcom_retval_str (rv));
+ }
+ else if (s->session_state == VCL_STATE_DISCONNECT)
+ {
+ vcl_send_session_reset_reply (wrk, s, 0);
+ }
+ else if (s->session_state == VCL_STATE_DETACHED)
+ {
+ /* Should not happen. VPP cleaned up before app confirmed close */
+ VDBG (0, "vpp freed session %d before close", s->session_index);
+ goto free_session;
+ }
+
+ s->session_state = VCL_STATE_CLOSED;
+
+ /* Session is removed only after vpp confirms the disconnect */
+ return rv;
+
+cleanup:
+ vcl_session_table_del_vpp_handle (wrk, s->vpp_handle);
+free_session:
+ vcl_session_free (wrk, s);
+ vcl_evt (VCL_EVT_CLOSE, s, rv);
+
+ return rv;
+}
+
+int
+vppcom_session_close (uint32_t session_handle)
+{
+ vcl_worker_t *wrk = vcl_worker_get_current ();
+ vcl_session_t *session;
+
+ session = vcl_session_get_w_handle (wrk, session_handle);
+ if (!session)
+ return VPPCOM_EBADFD;
+ return vcl_session_cleanup (wrk, session, session_handle,
+ 1 /* do_disconnect */ );
+}
+
+int
+vppcom_session_bind (uint32_t session_handle, vppcom_endpt_t * ep)
+{
+ vcl_worker_t *wrk = vcl_worker_get_current ();
+ vcl_session_t *session = 0;
+
+ if (!ep || !ep->ip)
+ return VPPCOM_EINVAL;