/* * Copyright (c) 2017-2018 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 #include #include #include #include #include #include #include #include #include #include typedef struct { vcl_test_session_t *sessions; uint32_t n_sessions; uint32_t wrk_index; fd_set wr_fdset; fd_set rd_fdset; int max_fd_index; pthread_t thread_handle; vcl_test_cfg_t cfg; } vcl_test_client_worker_t; typedef struct { vcl_test_client_worker_t *workers; vppcom_endpt_t server_endpt; uint32_t cfg_seq_num; vcl_test_session_t ctrl_session; vcl_test_session_t *sessions; uint8_t dump_cfg; vcl_test_t post_test; uint32_t proto; uint32_t n_workers; volatile int active_workers; struct sockaddr_storage server_addr; } vcl_test_client_main_t; static __thread int __wrk_index = 0; vcl_test_client_main_t vcl_client_main; #define vtc_min(a, b) (a < b ? a : b) #define vtc_max(a, b) (a > b ? a : b) static int vtc_cfg_sync (vcl_test_session_t * ts) { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_cfg_t *rx_cfg = (vcl_test_cfg_t *) ts->rxbuf; int rx_bytes, tx_bytes; vt_atomic_add (&ts->cfg.seq_num, 1); if (ts->cfg.verbose) { vtinf ("(fd %d): Sending config to server.", ts->fd); vcl_test_cfg_dump (&ts->cfg, 1 /* is_client */ ); } tx_bytes = vcl_test_write (ts->fd, (uint8_t *) & ts->cfg, sizeof (ts->cfg), NULL, ts->cfg.verbose); if (tx_bytes < 0) { vtwrn ("(fd %d): write test cfg failed (%d)!", ts->fd, tx_bytes); return tx_bytes; } rx_bytes = vcl_test_read (ts->fd, (uint8_t *) ts->rxbuf, sizeof (vcl_test_cfg_t), NULL); if (rx_bytes < 0) return rx_bytes; if (rx_cfg->magic != VCL_TEST_CFG_CTRL_MAGIC) { vtwrn ("(fd %d): Bad server reply cfg -- aborting!", ts->fd); return -1; } if ((rx_bytes != sizeof (vcl_test_cfg_t)) || !vcl_test_cfg_verify (rx_cfg, &ts->cfg)) { vtwrn ("(fd %d): Invalid config received from server!", ts->fd); if (rx_bytes != sizeof (vcl_test_cfg_t)) { vtinf ("\tRx bytes %d != cfg size %lu", rx_bytes, sizeof (vcl_test_cfg_t)); } else { vcl_test_cfg_dump (rx_cfg, 1 /* is_client */ ); vtinf ("(fd %d): Valid config sent to server.", ts->fd); vcl_test_cfg_dump (&ts->cfg, 1 /* is_client */ ); } return -1; } if (ts->cfg.verbose) { vtinf ("(fd %d): Got config back from server.", ts->fd); vcl_test_cfg_dump (rx_cfg, 1 /* is_client */ ); } return 0; } static int vtc_connect_test_sessions (vcl_test_client_worker_t * wrk) { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_session_t *ts; uint32_t n_test_sessions; int i, rv; n_test_sessions = wrk->cfg.num_test_sessions; if (n_test_sessions < 1) { errno = EINVAL; return -1; } if (wrk->n_sessions >= n_test_sessions) goto done; if (wrk->n_sessions) wrk->sessions = realloc (wrk->sessions, n_test_sessions * sizeof (vcl_test_session_t)); else wrk->sessions = calloc (n_test_sessions, sizeof (vcl_test_session_t)); if (!wrk->sessions) { vterr ("failed to alloc sessions", -errno); return errno; } for (i = 0; i < n_test_sessions; i++) { ts = &wrk->sessions[i]; ts->fd = vppcom_session_create (vcm->proto, 1 /* is_nonblocking */ ); if (ts->fd < 0) { vterr ("vppcom_session_create()", ts->fd); return ts->fd; } rv = vppcom_session_connect (ts->fd, &vcm->server_endpt); if (rv < 0) { vterr ("vppcom_session_connect()", rv); return rv; } vtinf ("Test session %d (fd %d) connected.", i, ts->fd); } wrk->n_sessions = n_test_sessions; done: vtinf ("All test sessions (%d) connected!", n_test_sessions); return 0; } static int vtc_worker_test_setup (vcl_test_client_worker_t * wrk) { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_session_t *ctrl = &vcm->ctrl_session; vcl_test_cfg_t *cfg = &wrk->cfg; vcl_test_session_t *ts; uint32_t sidx; int i, j; FD_ZERO (&wrk->wr_fdset); FD_ZERO (&wrk->rd_fdset); for (i = 0; i < cfg->num_test_sessions; i++) { ts = &wrk->sessions[i]; ts->cfg = wrk->cfg; vcl_test_session_buf_alloc (ts); switch (cfg->test) { case VCL_TEST_TYPE_ECHO: memcpy (ts->txbuf, ctrl->txbuf, cfg->total_bytes); break; case VCL_TEST_TYPE_UNI: case VCL_TEST_TYPE_BI: for (j = 0; j < ts->txbuf_size; j++) ts->txbuf[j] = j & 0xff; break; } FD_SET (vppcom_session_index (ts->fd), &wrk->wr_fdset); FD_SET (vppcom_session_index (ts->fd), &wrk->rd_fdset); sidx = vppcom_session_index (ts->fd); wrk->max_fd_index = vtc_max (sidx, wrk->max_fd_index); } wrk->max_fd_index += 1; return 0; } static int vtc_worker_init (vcl_test_client_worker_t * wrk) { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_session_t *ctrl = &vcm->ctrl_session; vcl_test_cfg_t *cfg = &wrk->cfg; vcl_test_session_t *ts; uint32_t i, n; int rv, nbytes; __wrk_index = wrk->wrk_index; vtinf ("Initializing worker %u ...", wrk->wrk_index); if (wrk->wrk_index) { if (vppcom_worker_register ()) { vtwrn ("failed to register worker"); return -1; } vt_atomic_add (&vcm->active_workers, 1); } rv = vtc_connect_test_sessions (wrk); if (rv) { vterr ("vtc_connect_test_sessions ()", rv); return rv; } if (vtc_worker_test_setup (wrk)) return -1; vtinf ("Sending config to server on all sessions ..."); for (n = 0; n < cfg->num_test_sessions; n++) { ts = &wrk->sessions[n]; if (vtc_cfg_sync (ts)) return -1; memset (&ts->stats, 0, sizeof (ts->stats)); } return 0; } static int stats_lock = 0; static void vtc_accumulate_stats (vcl_test_client_worker_t * wrk, vcl_test_session_t * ctrl) { vcl_test_session_t *ts; static char buf[64]; int i, show_rx = 0; while (__sync_lock_test_and_set (&stats_lock, 1)) ; if (ctrl->cfg.test == VCL_TEST_TYPE_BI || ctrl->cfg.test == VCL_TEST_TYPE_ECHO) show_rx = 1; for (i = 0; i < wrk->cfg.num_test_sessions; i++) { ts = &wrk->sessions[i]; ts->stats.start = ctrl->stats.start; if (ctrl->cfg.verbose > 1) { sprintf (buf, "CLIENT (fd %d) RESULTS", ts->fd); vcl_test_stats_dump (buf, &ts->stats, show_rx, 1 /* show tx */ , ctrl->cfg.verbose); } vcl_test_stats_accumulate (&ctrl->stats, &ts->stats); if (vcl_comp_tspec (&ctrl->stats.stop, &ts->stats.stop) < 0) ctrl->stats.stop = ts->stats.stop; } __sync_lock_release (&stats_lock); } static void vtc_worker_sessions_exit (vcl_test_client_worker_t * wrk) { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_session_t *ctrl = &vcm->ctrl_session; vcl_test_session_t *ts; int i, verbose = ctrl->cfg.verbose; for (i = 0; i < wrk->cfg.num_test_sessions; i++) { ts = &wrk->sessions[i]; ts->cfg.test = VCL_TEST_TYPE_EXIT; if (verbose) { vtinf ("(fd %d): Sending exit cfg to server...", ts->fd); vcl_test_cfg_dump (&ts->cfg, 1 /* is_client */ ); } (void) vcl_test_write (ts->fd, (uint8_t *) & ts->cfg, sizeof (ts->cfg), &ts->stats, verbose); } wrk->n_sessions = 0; } static void * vtc_worker_loop (void *arg) { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_session_t *ctrl = &vcm->ctrl_session; vcl_test_client_worker_t *wrk = arg; uint32_t n_active_sessions, n_bytes; fd_set _wfdset, *wfdset = &_wfdset; fd_set _rfdset, *rfdset = &_rfdset; vcl_test_session_t *ts; int i, rv, check_rx = 0; rv = vtc_worker_init (wrk); if (rv) { vterr ("vtc_worker_init()", rv); return 0; } vtinf ("Starting test ..."); if (wrk->wrk_index == 0) clock_gettime (CLOCK_REALTIME, &ctrl->stats.start); check_rx = wrk->cfg.test != VCL_TEST_TYPE_UNI; n_active_sessions = wrk->cfg.num_test_sessions; while (n_active_sessions) { _wfdset = wrk->wr_fdset; _rfdset = wrk->rd_fdset; rv = vppcom_select (wrk->max_fd_index, (uint64_t *) rfdset, (uint64_t *) wfdset, NULL, 0); if (rv < 0) { vterr ("vppcom_select()", rv); goto exit; } else if (rv == 0) continue; for (i = 0; i < wrk->cfg.num_test_sessions; i++) { ts = &wrk->sessions[i]; if (!((ts->stats.stop.tv_sec == 0) && (ts->stats.stop.tv_nsec == 0))) continue; if (FD_ISSET (vppcom_session_index (ts->fd), rfdset) && ts->stats.rx_bytes < ts->cfg.total_bytes) { (void) vcl_test_read (ts->fd, (uint8_t *) ts->rxbuf, ts->rxbuf_size, &ts->stats); } if (FD_ISSET (vppcom_session_index (ts->fd), wfdset) && ts->stats.tx_bytes < ts->cfg.total_bytes) { n_bytes = ts->cfg.txbuf_size; if (ts->cfg.test == VCL_TEST_TYPE_ECHO) n_bytes = strlen (ctrl->txbuf) + 1; rv = vcl_test_write (ts->fd, (uint8_t *) ts->txbuf, n_bytes, &ts->stats, ts->cfg.verbose); if (rv < 0) { vtwrn ("vppcom_test_write (%d) failed -- aborting test", ts->fd); goto exit; } } if ((!check_rx && ts->stats.tx_bytes >= ts->cfg.total_bytes) || (check_rx && ts->stats.rx_bytes >= ts->cfg.total_bytes)) { clock_gettime (CLOCK_REALTIME, &ts->stats.stop); n_active_sessions--; } } } exit: vtinf ("Worker %d done ...", wrk->wrk_index); if (wrk->cfg.test != VCL_TEST_TYPE_ECHO) vtc_accumulate_stats (wrk, ctrl); sleep (VCL_TEST_DELAY_DISCONNECT); vtc_worker_sessions_exit (wrk); if (wrk->wrk_index) vt_atomic_add (&vcm->active_workers, -1); return 0; } static void vtc_print_stats (vcl_test_session_t * ctrl) { int is_echo = ctrl->cfg.test == VCL_TEST_TYPE_ECHO; int show_rx = 0; char buf[64]; if (ctrl->cfg.test == VCL_TEST_TYPE_BI || ctrl->cfg.test == VCL_TEST_TYPE_ECHO) show_rx = 1; vcl_test_stats_dump ("CLIENT RESULTS", &ctrl->stats, show_rx, 1 /* show tx */ , ctrl->cfg.verbose); vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); if (ctrl->cfg.verbose) { vtinf (" ctrl session info\n" VCL_TEST_SEPARATOR_STRING " fd: %d (0x%08x)\n" " rxbuf: %p\n" " rxbuf size: %u (0x%08x)\n" " txbuf: %p\n" " txbuf size: %u (0x%08x)\n" VCL_TEST_SEPARATOR_STRING, ctrl->fd, (uint32_t) ctrl->fd, ctrl->rxbuf, ctrl->rxbuf_size, ctrl->rxbuf_size, ctrl->txbuf, ctrl->txbuf_size, ctrl->txbuf_size); } if (is_echo) sprintf (buf, "Echo"); else sprintf (buf, "%s-directional Stream", ctrl->cfg.test == VCL_TEST_TYPE_BI ? "Bi" : "Uni"); } static void vtc_echo_client (vcl_test_client_main_t * vcm) { vcl_test_client_worker_t *wrk; vcl_test_session_t *ctrl = &vcm->ctrl_session; vcl_test_cfg_t *cfg = &ctrl->cfg; cfg->total_bytes = strlen (ctrl->txbuf) + 1; memset (&ctrl->stats, 0, sizeof (ctrl->stats)); /* Echo works with only one worker */ wrk = vcm->workers; wrk->wrk_index = 0; wrk->cfg = *cfg; vtc_worker_loop (wrk); /* Not relevant for echo test clock_gettime (CLOCK_REALTIME, &ctrl->stats.stop); vtc_accumulate_stats (wrk, ctrl); vtc_print_stats (ctrl); */ } static void vtc_stream_client (vcl_test_client_main_t * vcm) { vcl_test_session_t *ctrl = &vcm->ctrl_session; vcl_test_cfg_t *cfg = &ctrl->cfg; vcl_test_client_worker_t *wrk; vcl_test_session_t *ts; int tx_bytes, rv; uint32_t i, n, sidx, n_conn, n_conn_per_wrk; vtinf ("%s-directional Stream Test Starting!", ctrl->cfg.test == VCL_TEST_TYPE_BI ? "Bi" : "Uni"); cfg->total_bytes = cfg->num_writes * cfg->txbuf_size; cfg->ctrl_handle = ~0; if (vtc_cfg_sync (ctrl)) { vtwrn ("test cfg sync failed -- aborting!"); return; } cfg->ctrl_handle = ((vcl_test_cfg_t *) ctrl->rxbuf)->ctrl_handle; memset (&ctrl->stats, 0, sizeof (ctrl->stats)); n_conn = cfg->num_test_sessions; n_conn_per_wrk = n_conn / vcm->n_workers; for (i = 0; i < vcm->n_workers; i++) { wrk = &vcm->workers[i]; wrk->wrk_index = i; wrk->cfg = ctrl->cfg; wrk->cfg.num_test_sessions = vtc_min (n_conn_per_wrk, n_conn); n_conn -= wrk->cfg.num_test_sessions; } for (i = 1; i < vcm->n_workers; i++) { wrk = &vcm->workers[i]; pthread_create (&wrk->thread_handle, NULL, vtc_worker_loop, (void *) wrk); } vtc_worker_loop (&vcm->workers[0]); while (vcm->active_workers > 0) ; vtinf ("Sending config on ctrl session (fd %d) for stats...", ctrl->fd); if (vtc_cfg_sync (ctrl)) { vtwrn ("test cfg sync failed -- aborting!"); return; } vtc_print_stats (ctrl); ctrl->cfg.test = VCL_TEST_TYPE_ECHO; ctrl->cfg.total_bytes = 0; if (vtc_cfg_sync (ctrl)) vtwrn ("post-test cfg sync failed!"); } static void dump_help (void) { #define INDENT "\n " printf ("CLIENT: Test configuration commands:" INDENT VCL_TEST_TOKEN_HELP "\t\t\tDisplay help." INDENT VCL_TEST_TOKEN_EXIT "\t\t\tExit test client & server." INDENT VCL_TEST_TOKEN_SHOW_CFG "\t\t\tShow the current test cfg." INDENT VCL_TEST_TOKEN_RUN_UNI "\t\t\tRun the Uni-directional test." INDENT VCL_TEST_TOKEN_RUN_BI "\t\t\tRun the Bi-directional test." INDENT VCL_TEST_TOKEN_VERBOSE "\t\t\tToggle verbose setting." INDENT VCL_TEST_TOKEN_RXBUF_SIZE "\tRx buffer size (bytes)." INDENT VCL_TEST_TOKEN_TXBUF_SIZE "\tTx buffer size (bytes)." INDENT VCL_TEST_TOKEN_NUM_WRITES "<# of writes>\tNumber of txbuf writes to server." "\n"); } static void cfg_txbuf_size_set (void) { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_session_t *ctrl = &vcm->ctrl_session; char *p = ctrl->txbuf + strlen (VCL_TEST_TOKEN_TXBUF_SIZE); uint64_t txbuf_size = strtoull ((const char *) p, NULL, 10); if (txbuf_size >= VCL_TEST_CFG_BUF_SIZE_MIN) { ctrl->cfg.txbuf_size = txbuf_size; ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size; vcl_test_buf_alloc (&ctrl->cfg, 0 /* is_rxbuf */ , (uint8_t **) & ctrl->txbuf, &ctrl->txbuf_size); vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); } else vtwrn ("Invalid txbuf size (%lu) < minimum buf size (%u)!", txbuf_size, VCL_TEST_CFG_BUF_SIZE_MIN); } static void cfg_num_writes_set (void) { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_session_t *ctrl = &vcm->ctrl_session; char *p = ctrl->txbuf + strlen (VCL_TEST_TOKEN_NUM_WRITES); uint32_t num_writes = strtoul ((const char *) p, NULL, 10); if (num_writes > 0) { ctrl->cfg.num_writes = num_writes; ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size; vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); } else { vtwrn ("invalid num writes: %u", num_writes); } } static void cfg_num_test_sessions_set (void) { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_session_t *ctrl = &vcm->ctrl_session; char *p = ctrl->txbuf + strlen (VCL_TEST_TOKEN_NUM_TEST_SESS); uint32_t num_test_sessions = strtoul ((const char *) p, NULL, 10); if ((num_test_sessions > 0) && (num_test_sessions <= VCL_TEST_CFG_MAX_TEST_SESS)) { ctrl->cfg.num_test_sessions = num_test_sessions; vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); } else { vtwrn ("invalid num test sessions: %u, (%d max)", num_test_sessions, VCL_TEST_CFG_MAX_TEST_SESS); } } static void cfg_rxbuf_size_set (void) { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_session_t *ctrl = &vcm->ctrl_session; char *p = ctrl->txbuf + strlen (VCL_TEST_TOKEN_RXBUF_SIZE); uint64_t rxbuf_size = strtoull ((const char *) p, NULL, 10); if (rxbuf_size >= VCL_TEST_CFG_BUF_SIZE_MIN) { ctrl->cfg.rxbuf_size = rxbuf_size; vcl_test_buf_alloc (&ctrl->cfg, 1 /* is_rxbuf */ , (uint8_t **) & ctrl->rxbuf, &ctrl->rxbuf_size); vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); } else vtwrn ("Invalid rxbuf size (%lu) < minimum buf size (%u)!", rxbuf_size, VCL_TEST_CFG_BUF_SIZE_MIN); } static void cfg_verbose_toggle (void) { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_session_t *ctrl = &vcm->ctrl_session; ctrl->cfg.verbose = ctrl->cfg.verbose ? 0 : 1; vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); } static vcl_test_t parse_input () { vcl_test_client_main_t *vcm = &vcl_client_main; vcl_test_session_t *ctrl = &vcm->ctrl_session; vcl_tes
/*
 * Copyright (c) 2015 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 <vnet/vnet.h>
#include <vnet/devices/devices.h>
#include <vnet/feature/feature.h>
#include <vnet/ip/ip.h>
#include <vnet/ethernet/ethernet.h>

vnet_device_main_t vnet_device_main;

static uword
device_input_fn (vlib_main_t * vm, vlib_node_runtim