freebsd-dev/tests/sys/netmap/ctrl-api-test.c
Vincenzo Maffione e2a431a0ff netmap: fix copyin/copyout of nmreq options list
The previous code unsuccesfully attempted to report a precise error for
each option in the user list. Moreover, commit 253b2ec199 broke some
ctrl-api-test (see https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=260547).

With this patch we bail out as soon as an unrecoverable error is detected and
we properly check for copy boundaries. EOPNOTSUPP no longer immediately
returns an error, so that any other option in the list may be examined
by the caller code and a precise report of the (un)supported options can
be returned to the user.

With this patch, all ctrl-api-test unit tests pass again.

PR:			260547
Submitted by:		giuseppe.lettieri@unipi.it
Reviewed by:		vmaffione
MFC after:		14 days
2023-03-21 23:23:18 +00:00

2328 lines
56 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (C) 2018 Vincenzo Maffione
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
/*
* This program contains a suite of unit tests for the netmap control device.
*
* On FreeBSD, you can run these tests with Kyua once installed in the system:
* # kyua test -k /usr/tests/sys/netmap/Kyuafile
*
* On Linux, you can run them directly:
* # ./ctrl-api-test
*/
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <libnetmap.h>
#include <net/if.h>
#include <net/netmap.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <stddef.h>
#ifdef __FreeBSD__
#include "freebsd_test_suite/macros.h"
static int
eventfd(int x __unused, int y __unused)
{
errno = ENODEV;
return -1;
}
#else /* __linux__ */
#include <sys/eventfd.h>
#endif
#define NM_IFNAMSZ 64
static int
exec_command(int argc, const char *const argv[])
{
pid_t child_pid;
pid_t wret;
int child_status;
int i;
printf("Executing command: ");
for (i = 0; i < argc - 1; i++) {
if (!argv[i]) {
/* Invalid argument. */
return -1;
}
if (i > 0) {
putchar(' ');
}
printf("%s", argv[i]);
}
putchar('\n');
child_pid = fork();
if (child_pid == 0) {
char **av;
int fds[3];
/* Child process. Redirect stdin, stdout
* and stderr. */
for (i = 0; i < 3; i++) {
close(i);
fds[i] = open("/dev/null", O_RDONLY);
if (fds[i] < 0) {
for (i--; i >= 0; i--) {
close(fds[i]);
}
return -1;
}
}
/* Make a copy of the arguments, passing them to execvp. */
av = calloc(argc, sizeof(av[0]));
if (!av) {
exit(EXIT_FAILURE);
}
for (i = 0; i < argc - 1; i++) {
av[i] = strdup(argv[i]);
if (!av[i]) {
exit(EXIT_FAILURE);
}
}
execvp(av[0], av);
perror("execvp()");
exit(EXIT_FAILURE);
}
wret = waitpid(child_pid, &child_status, 0);
if (wret < 0) {
fprintf(stderr, "waitpid() failed: %s\n", strerror(errno));
return wret;
}
if (WIFEXITED(child_status)) {
return WEXITSTATUS(child_status);
}
return -1;
}
#define THRET_SUCCESS ((void *)128)
#define THRET_FAILURE ((void *)0)
struct TestContext {
char ifname[NM_IFNAMSZ];
char ifname_ext[NM_IFNAMSZ];
char bdgname[NM_IFNAMSZ];
uint32_t nr_tx_slots; /* slots in tx rings */
uint32_t nr_rx_slots; /* slots in rx rings */
uint16_t nr_tx_rings; /* number of tx rings */
uint16_t nr_rx_rings; /* number of rx rings */
uint16_t nr_host_tx_rings; /* number of host tx rings */
uint16_t nr_host_rx_rings; /* number of host rx rings */
uint16_t nr_mem_id; /* id of the memory allocator */
uint16_t nr_ringid; /* ring(s) we care about */
uint32_t nr_mode; /* specify NR_REG_* modes */
uint32_t nr_extra_bufs; /* number of requested extra buffers */
uint64_t nr_flags; /* additional flags (see below) */
uint32_t nr_hdr_len; /* for PORT_HDR_SET and PORT_HDR_GET */
uint32_t nr_first_cpu_id; /* vale polling */
uint32_t nr_num_polling_cpus; /* vale polling */
uint32_t sync_kloop_mode; /* sync-kloop */
int fd; /* netmap file descriptor */
void *csb; /* CSB entries (atok and ktoa) */
struct nmreq_option *nr_opt; /* list of options */
sem_t *sem; /* for thread synchronization */
struct nmctx *nmctx;
const char *ifparse;
struct nmport_d *nmport; /* nmport descriptor from libnetmap */
};
static struct TestContext ctx_;
typedef int (*testfunc_t)(struct TestContext *ctx);
static void
nmreq_hdr_init(struct nmreq_header *hdr, const char *ifname)
{
memset(hdr, 0, sizeof(*hdr));
hdr->nr_version = NETMAP_API;
assert(strlen(ifname) < NM_IFNAMSZ);
strncpy(hdr->nr_name, ifname, sizeof(hdr->nr_name));
}
/* Single NETMAP_REQ_PORT_INFO_GET. */
static int
port_info_get(struct TestContext *ctx)
{
struct nmreq_port_info_get req;
struct nmreq_header hdr;
int success;
int ret;
printf("Testing NETMAP_REQ_PORT_INFO_GET on '%s'\n", ctx->ifname_ext);
nmreq_hdr_init(&hdr, ctx->ifname_ext);
hdr.nr_reqtype = NETMAP_REQ_PORT_INFO_GET;
hdr.nr_body = (uintptr_t)&req;
memset(&req, 0, sizeof(req));
req.nr_mem_id = ctx->nr_mem_id;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, PORT_INFO_GET)");
return ret;
}
printf("nr_memsize %llu\n", (unsigned long long)req.nr_memsize);
printf("nr_tx_slots %u\n", req.nr_tx_slots);
printf("nr_rx_slots %u\n", req.nr_rx_slots);
printf("nr_tx_rings %u\n", req.nr_tx_rings);
printf("nr_rx_rings %u\n", req.nr_rx_rings);
printf("nr_mem_id %u\n", req.nr_mem_id);
success = req.nr_memsize && req.nr_tx_slots && req.nr_rx_slots &&
req.nr_tx_rings && req.nr_rx_rings && req.nr_tx_rings;
if (!success) {
return -1;
}
/* Write back results to the context structure. */
ctx->nr_tx_slots = req.nr_tx_slots;
ctx->nr_rx_slots = req.nr_rx_slots;
ctx->nr_tx_rings = req.nr_tx_rings;
ctx->nr_rx_rings = req.nr_rx_rings;
ctx->nr_mem_id = req.nr_mem_id;
return 0;
}
/* Single NETMAP_REQ_REGISTER, no use. */
static int
port_register(struct TestContext *ctx)
{
struct nmreq_register req;
struct nmreq_header hdr;
int success;
int ret;
printf("Testing NETMAP_REQ_REGISTER(mode=%d,ringid=%d,"
"flags=0x%llx) on '%s'\n",
ctx->nr_mode, ctx->nr_ringid, (unsigned long long)ctx->nr_flags,
ctx->ifname_ext);
nmreq_hdr_init(&hdr, ctx->ifname_ext);
hdr.nr_reqtype = NETMAP_REQ_REGISTER;
hdr.nr_body = (uintptr_t)&req;
hdr.nr_options = (uintptr_t)ctx->nr_opt;
memset(&req, 0, sizeof(req));
req.nr_mem_id = ctx->nr_mem_id;
req.nr_mode = ctx->nr_mode;
req.nr_ringid = ctx->nr_ringid;
req.nr_flags = ctx->nr_flags;
req.nr_tx_slots = ctx->nr_tx_slots;
req.nr_rx_slots = ctx->nr_rx_slots;
req.nr_tx_rings = ctx->nr_tx_rings;
req.nr_host_tx_rings = ctx->nr_host_tx_rings;
req.nr_host_rx_rings = ctx->nr_host_rx_rings;
req.nr_rx_rings = ctx->nr_rx_rings;
req.nr_extra_bufs = ctx->nr_extra_bufs;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, REGISTER)");
return ret;
}
printf("nr_offset 0x%llx\n", (unsigned long long)req.nr_offset);
printf("nr_memsize %llu\n", (unsigned long long)req.nr_memsize);
printf("nr_tx_slots %u\n", req.nr_tx_slots);
printf("nr_rx_slots %u\n", req.nr_rx_slots);
printf("nr_tx_rings %u\n", req.nr_tx_rings);
printf("nr_rx_rings %u\n", req.nr_rx_rings);
printf("nr_host_tx_rings %u\n", req.nr_host_tx_rings);
printf("nr_host_rx_rings %u\n", req.nr_host_rx_rings);
printf("nr_mem_id %u\n", req.nr_mem_id);
printf("nr_extra_bufs %u\n", req.nr_extra_bufs);
success = req.nr_memsize && (ctx->nr_mode == req.nr_mode) &&
(ctx->nr_ringid == req.nr_ringid) &&
(ctx->nr_flags == req.nr_flags) &&
((!ctx->nr_tx_slots && req.nr_tx_slots) ||
(ctx->nr_tx_slots == req.nr_tx_slots)) &&
((!ctx->nr_rx_slots && req.nr_rx_slots) ||
(ctx->nr_rx_slots == req.nr_rx_slots)) &&
((!ctx->nr_tx_rings && req.nr_tx_rings) ||
(ctx->nr_tx_rings == req.nr_tx_rings)) &&
((!ctx->nr_rx_rings && req.nr_rx_rings) ||
(ctx->nr_rx_rings == req.nr_rx_rings)) &&
((!ctx->nr_host_tx_rings && req.nr_host_tx_rings) ||
(ctx->nr_host_tx_rings == req.nr_host_tx_rings)) &&
((!ctx->nr_host_rx_rings && req.nr_host_rx_rings) ||
(ctx->nr_host_rx_rings == req.nr_host_rx_rings)) &&
((!ctx->nr_mem_id && req.nr_mem_id) ||
(ctx->nr_mem_id == req.nr_mem_id)) &&
(ctx->nr_extra_bufs == req.nr_extra_bufs);
if (!success) {
return -1;
}
/* Write back results to the context structure.*/
ctx->nr_tx_slots = req.nr_tx_slots;
ctx->nr_rx_slots = req.nr_rx_slots;
ctx->nr_tx_rings = req.nr_tx_rings;
ctx->nr_rx_rings = req.nr_rx_rings;
ctx->nr_host_tx_rings = req.nr_host_tx_rings;
ctx->nr_host_rx_rings = req.nr_host_rx_rings;
ctx->nr_mem_id = req.nr_mem_id;
ctx->nr_extra_bufs = req.nr_extra_bufs;
return 0;
}
static int
niocregif(struct TestContext *ctx, int netmap_api)
{
struct nmreq req;
int success;
int ret;
printf("Testing legacy NIOCREGIF on '%s'\n", ctx->ifname_ext);
memset(&req, 0, sizeof(req));
memcpy(req.nr_name, ctx->ifname_ext, sizeof(req.nr_name));
req.nr_name[sizeof(req.nr_name) - 1] = '\0';
req.nr_version = netmap_api;
req.nr_ringid = ctx->nr_ringid;
req.nr_flags = ctx->nr_mode | ctx->nr_flags;
req.nr_tx_slots = ctx->nr_tx_slots;
req.nr_rx_slots = ctx->nr_rx_slots;
req.nr_tx_rings = ctx->nr_tx_rings;
req.nr_rx_rings = ctx->nr_rx_rings;
req.nr_arg2 = ctx->nr_mem_id;
req.nr_arg3 = ctx->nr_extra_bufs;
ret = ioctl(ctx->fd, NIOCREGIF, &req);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCREGIF)");
return ret;
}
printf("nr_offset 0x%x\n", req.nr_offset);
printf("nr_memsize %u\n", req.nr_memsize);
printf("nr_tx_slots %u\n", req.nr_tx_slots);
printf("nr_rx_slots %u\n", req.nr_rx_slots);
printf("nr_tx_rings %u\n", req.nr_tx_rings);
printf("nr_rx_rings %u\n", req.nr_rx_rings);
printf("nr_version %d\n", req.nr_version);
printf("nr_ringid %x\n", req.nr_ringid);
printf("nr_flags %x\n", req.nr_flags);
printf("nr_arg2 %u\n", req.nr_arg2);
printf("nr_arg3 %u\n", req.nr_arg3);
success = req.nr_memsize &&
(ctx->nr_ringid == req.nr_ringid) &&
((ctx->nr_mode | ctx->nr_flags) == req.nr_flags) &&
((!ctx->nr_tx_slots && req.nr_tx_slots) ||
(ctx->nr_tx_slots == req.nr_tx_slots)) &&
((!ctx->nr_rx_slots && req.nr_rx_slots) ||
(ctx->nr_rx_slots == req.nr_rx_slots)) &&
((!ctx->nr_tx_rings && req.nr_tx_rings) ||
(ctx->nr_tx_rings == req.nr_tx_rings)) &&
((!ctx->nr_rx_rings && req.nr_rx_rings) ||
(ctx->nr_rx_rings == req.nr_rx_rings)) &&
((!ctx->nr_mem_id && req.nr_arg2) ||
(ctx->nr_mem_id == req.nr_arg2)) &&
(ctx->nr_extra_bufs == req.nr_arg3);
if (!success) {
return -1;
}
/* Write back results to the context structure.*/
ctx->nr_tx_slots = req.nr_tx_slots;
ctx->nr_rx_slots = req.nr_rx_slots;
ctx->nr_tx_rings = req.nr_tx_rings;
ctx->nr_rx_rings = req.nr_rx_rings;
ctx->nr_mem_id = req.nr_arg2;
ctx->nr_extra_bufs = req.nr_arg3;
return ret;
}
/* The 11 ABI is the one right before the introduction of the new NIOCCTRL
* ABI. The 11 ABI is useful to perform tests with legacy applications
* (which use the 11 ABI) and new kernel (which uses 12, or higher).
* However, version 14 introduced a change in the layout of struct netmap_if,
* so that binary backward compatibility to 11 is not supported anymore.
*/
#define NETMAP_API_NIOCREGIF 14
static int
legacy_regif_default(struct TestContext *ctx)
{
return niocregif(ctx, NETMAP_API_NIOCREGIF);
}
static int
legacy_regif_all_nic(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_ALL_NIC;
return niocregif(ctx, NETMAP_API);
}
static int
legacy_regif_12(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_ALL_NIC;
return niocregif(ctx, NETMAP_API_NIOCREGIF+1);
}
static int
legacy_regif_sw(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_SW;
return niocregif(ctx, NETMAP_API_NIOCREGIF);
}
static int
legacy_regif_future(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_NIC_SW;
/* Test forward compatibility for the legacy ABI. This means
* using an older kernel (with ABI 12 or higher) and a newer
* application (with ABI greater than NETMAP_API). */
return niocregif(ctx, NETMAP_API+2);
}
static int
legacy_regif_extra_bufs(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_ALL_NIC;
ctx->nr_extra_bufs = 20; /* arbitrary number of extra bufs */
return niocregif(ctx, NETMAP_API_NIOCREGIF);
}
static int
legacy_regif_extra_bufs_pipe(struct TestContext *ctx)
{
strncat(ctx->ifname_ext, "{pipeexbuf", sizeof(ctx->ifname_ext));
ctx->nr_mode = NR_REG_ALL_NIC;
ctx->nr_extra_bufs = 58; /* arbitrary number of extra bufs */
return niocregif(ctx, NETMAP_API_NIOCREGIF);
}
static int
legacy_regif_extra_bufs_pipe_vale(struct TestContext *ctx)
{
strncpy(ctx->ifname_ext, "valeX1:Y4", sizeof(ctx->ifname_ext));
return legacy_regif_extra_bufs_pipe(ctx);
}
/* Only valid after a successful port_register(). */
static int
num_registered_rings(struct TestContext *ctx)
{
if (ctx->nr_flags & NR_TX_RINGS_ONLY) {
return ctx->nr_tx_rings;
}
if (ctx->nr_flags & NR_RX_RINGS_ONLY) {
return ctx->nr_rx_rings;
}
return ctx->nr_tx_rings + ctx->nr_rx_rings;
}
static int
port_register_hwall_host(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_NIC_SW;
return port_register(ctx);
}
static int
port_register_hostall(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_SW;
return port_register(ctx);
}
static int
port_register_hwall(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_ALL_NIC;
return port_register(ctx);
}
static int
port_register_single_hw_pair(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_ONE_NIC;
ctx->nr_ringid = 0;
return port_register(ctx);
}
static int
port_register_single_host_pair(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_ONE_SW;
ctx->nr_host_tx_rings = 2;
ctx->nr_host_rx_rings = 2;
ctx->nr_ringid = 1;
return port_register(ctx);
}
static int
port_register_hostall_many(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_SW;
ctx->nr_host_tx_rings = 5;
ctx->nr_host_rx_rings = 4;
return port_register(ctx);
}
static int
port_register_hwall_tx(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_ALL_NIC;
ctx->nr_flags |= NR_TX_RINGS_ONLY;
return port_register(ctx);
}
static int
port_register_hwall_rx(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_ALL_NIC;
ctx->nr_flags |= NR_RX_RINGS_ONLY;
return port_register(ctx);
}
static int
vale_mkname(char *vpname, struct TestContext *ctx)
{
if (snprintf(vpname, NM_IFNAMSZ, "%s:%s", ctx->bdgname, ctx->ifname_ext) >= NM_IFNAMSZ) {
fprintf(stderr, "%s:%s too long (max %d chars)\n", ctx->bdgname, ctx->ifname_ext,
NM_IFNAMSZ - 1);
return -1;
}
return 0;
}
/* NETMAP_REQ_VALE_ATTACH */
static int
vale_attach(struct TestContext *ctx)
{
struct nmreq_vale_attach req;
struct nmreq_header hdr;
char vpname[NM_IFNAMSZ];
int ret;
if (vale_mkname(vpname, ctx) < 0)
return -1;
printf("Testing NETMAP_REQ_VALE_ATTACH on '%s'\n", vpname);
nmreq_hdr_init(&hdr, vpname);
hdr.nr_reqtype = NETMAP_REQ_VALE_ATTACH;
hdr.nr_body = (uintptr_t)&req;
memset(&req, 0, sizeof(req));
req.reg.nr_mem_id = ctx->nr_mem_id;
if (ctx->nr_mode == 0) {
ctx->nr_mode = NR_REG_ALL_NIC; /* default */
}
req.reg.nr_mode = ctx->nr_mode;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, VALE_ATTACH)");
return ret;
}
printf("nr_mem_id %u\n", req.reg.nr_mem_id);
return ((!ctx->nr_mem_id && req.reg.nr_mem_id > 1) ||
(ctx->nr_mem_id == req.reg.nr_mem_id)) &&
(ctx->nr_flags == req.reg.nr_flags)
? 0
: -1;
}
/* NETMAP_REQ_VALE_DETACH */
static int
vale_detach(struct TestContext *ctx)
{
struct nmreq_header hdr;
struct nmreq_vale_detach req;
char vpname[NM_IFNAMSZ];
int ret;
if (vale_mkname(vpname, ctx) < 0)
return -1;
printf("Testing NETMAP_REQ_VALE_DETACH on '%s'\n", vpname);
nmreq_hdr_init(&hdr, vpname);
hdr.nr_reqtype = NETMAP_REQ_VALE_DETACH;
hdr.nr_body = (uintptr_t)&req;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, VALE_DETACH)");
return ret;
}
return 0;
}
/* First NETMAP_REQ_VALE_ATTACH, then NETMAP_REQ_VALE_DETACH. */
static int
vale_attach_detach(struct TestContext *ctx)
{
int ret;
if ((ret = vale_attach(ctx)) != 0) {
return ret;
}
return vale_detach(ctx);
}
static int
vale_attach_detach_host_rings(struct TestContext *ctx)
{
ctx->nr_mode = NR_REG_NIC_SW;
return vale_attach_detach(ctx);
}
/* First NETMAP_REQ_PORT_HDR_SET and the NETMAP_REQ_PORT_HDR_GET
* to check that we get the same value. */
static int
port_hdr_set_and_get(struct TestContext *ctx)
{
struct nmreq_port_hdr req;
struct nmreq_header hdr;
int ret;
printf("Testing NETMAP_REQ_PORT_HDR_SET on '%s'\n", ctx->ifname_ext);
nmreq_hdr_init(&hdr, ctx->ifname_ext);
hdr.nr_reqtype = NETMAP_REQ_PORT_HDR_SET;
hdr.nr_body = (uintptr_t)&req;
memset(&req, 0, sizeof(req));
req.nr_hdr_len = ctx->nr_hdr_len;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, PORT_HDR_SET)");
return ret;
}
if (req.nr_hdr_len != ctx->nr_hdr_len) {
return -1;
}
printf("Testing NETMAP_REQ_PORT_HDR_GET on '%s'\n", ctx->ifname_ext);
hdr.nr_reqtype = NETMAP_REQ_PORT_HDR_GET;
req.nr_hdr_len = 0;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, PORT_HDR_SET)");
return ret;
}
printf("nr_hdr_len %u\n", req.nr_hdr_len);
return (req.nr_hdr_len == ctx->nr_hdr_len) ? 0 : -1;
}
/*
* Possible lengths for the VirtIO network header, as specified by
* the standard:
* http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html
*/
#define VIRTIO_NET_HDR_LEN 10
#define VIRTIO_NET_HDR_LEN_WITH_MERGEABLE_RXBUFS 12
static int
vale_ephemeral_port_hdr_manipulation(struct TestContext *ctx)
{
int ret;
strncpy(ctx->ifname_ext, "vale:eph0", sizeof(ctx->ifname_ext));
ctx->nr_mode = NR_REG_ALL_NIC;
if ((ret = port_register(ctx))) {
return ret;
}
/* Try to set and get all the acceptable values. */
ctx->nr_hdr_len = VIRTIO_NET_HDR_LEN_WITH_MERGEABLE_RXBUFS;
if ((ret = port_hdr_set_and_get(ctx))) {
return ret;
}
ctx->nr_hdr_len = 0;
if ((ret = port_hdr_set_and_get(ctx))) {
return ret;
}
ctx->nr_hdr_len = VIRTIO_NET_HDR_LEN;
if ((ret = port_hdr_set_and_get(ctx))) {
return ret;
}
return 0;
}
static int
vale_persistent_port(struct TestContext *ctx)
{
struct nmreq_vale_newif req;
struct nmreq_header hdr;
int result;
int ret;
strncpy(ctx->ifname_ext, "per4", sizeof(ctx->ifname_ext));
printf("Testing NETMAP_REQ_VALE_NEWIF on '%s'\n", ctx->ifname_ext);
nmreq_hdr_init(&hdr, ctx->ifname_ext);
hdr.nr_reqtype = NETMAP_REQ_VALE_NEWIF;
hdr.nr_body = (uintptr_t)&req;
memset(&req, 0, sizeof(req));
req.nr_mem_id = ctx->nr_mem_id;
req.nr_tx_slots = ctx->nr_tx_slots;
req.nr_rx_slots = ctx->nr_rx_slots;
req.nr_tx_rings = ctx->nr_tx_rings;
req.nr_rx_rings = ctx->nr_rx_rings;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, VALE_NEWIF)");
return ret;
}
/* Attach the persistent VALE port to a switch and then detach. */
result = vale_attach_detach(ctx);
printf("Testing NETMAP_REQ_VALE_DELIF on '%s'\n", ctx->ifname_ext);
hdr.nr_reqtype = NETMAP_REQ_VALE_DELIF;
hdr.nr_body = (uintptr_t)NULL;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, VALE_NEWIF)");
if (result == 0) {
result = ret;
}
}
return result;
}
/* Single NETMAP_REQ_POOLS_INFO_GET. */
static int
pools_info_get(struct TestContext *ctx)
{
struct nmreq_pools_info req;
struct nmreq_header hdr;
int ret;
printf("Testing NETMAP_REQ_POOLS_INFO_GET on '%s'\n", ctx->ifname_ext);
nmreq_hdr_init(&hdr, ctx->ifname_ext);
hdr.nr_reqtype = NETMAP_REQ_POOLS_INFO_GET;
hdr.nr_body = (uintptr_t)&req;
memset(&req, 0, sizeof(req));
req.nr_mem_id = ctx->nr_mem_id;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, POOLS_INFO_GET)");
return ret;
}
printf("nr_memsize %llu\n", (unsigned long long)req.nr_memsize);
printf("nr_mem_id %u\n", req.nr_mem_id);
printf("nr_if_pool_offset 0x%llx\n",
(unsigned long long)req.nr_if_pool_offset);
printf("nr_if_pool_objtotal %u\n", req.nr_if_pool_objtotal);
printf("nr_if_pool_objsize %u\n", req.nr_if_pool_objsize);
printf("nr_ring_pool_offset 0x%llx\n",
(unsigned long long)req.nr_if_pool_offset);
printf("nr_ring_pool_objtotal %u\n", req.nr_ring_pool_objtotal);
printf("nr_ring_pool_objsize %u\n", req.nr_ring_pool_objsize);
printf("nr_buf_pool_offset 0x%llx\n",
(unsigned long long)req.nr_buf_pool_offset);
printf("nr_buf_pool_objtotal %u\n", req.nr_buf_pool_objtotal);
printf("nr_buf_pool_objsize %u\n", req.nr_buf_pool_objsize);
return req.nr_memsize && req.nr_if_pool_objtotal &&
req.nr_if_pool_objsize &&
req.nr_ring_pool_objtotal &&
req.nr_ring_pool_objsize &&
req.nr_buf_pool_objtotal &&
req.nr_buf_pool_objsize
? 0
: -1;
}
static int
pools_info_get_and_register(struct TestContext *ctx)
{
int ret;
/* Check that we can get pools info before we register
* a netmap interface. */
ret = pools_info_get(ctx);
if (ret != 0) {
return ret;
}
ctx->nr_mode = NR_REG_ONE_NIC;
ret = port_register(ctx);
if (ret != 0) {
return ret;
}
ctx->nr_mem_id = 1;
/* Check that we can get pools info also after we register. */
return pools_info_get(ctx);
}
static int
pools_info_get_empty_ifname(struct TestContext *ctx)
{
strncpy(ctx->ifname_ext, "", sizeof(ctx->ifname_ext));
return pools_info_get(ctx) != 0 ? 0 : -1;
}
static int
pipe_master(struct TestContext *ctx)
{
strncat(ctx->ifname_ext, "{pipeid1", sizeof(ctx->ifname_ext));
ctx->nr_mode = NR_REG_NIC_SW;
if (port_register(ctx) == 0) {
printf("pipes should not accept NR_REG_NIC_SW\n");
return -1;
}
ctx->nr_mode = NR_REG_ALL_NIC;
return port_register(ctx);
}
static int
pipe_slave(struct TestContext *ctx)
{
strncat(ctx->ifname_ext, "}pipeid2", sizeof(ctx->ifname_ext));
ctx->nr_mode = NR_REG_ALL_NIC;
return port_register(ctx);
}
/* Test PORT_INFO_GET and POOLS_INFO_GET on a pipe. This is useful to test the
* registration request used internally by netmap. */
static int
pipe_port_info_get(struct TestContext *ctx)
{
strncat(ctx->ifname_ext, "}pipeid3", sizeof(ctx->ifname_ext));
return port_info_get(ctx);
}
static int
pipe_pools_info_get(struct TestContext *ctx)
{
strncat(ctx->ifname_ext, "{xid", sizeof(ctx->ifname_ext));
return pools_info_get(ctx);
}
/* NETMAP_REQ_VALE_POLLING_ENABLE */
static int
vale_polling_enable(struct TestContext *ctx)
{
struct nmreq_vale_polling req;
struct nmreq_header hdr;
char vpname[NM_IFNAMSZ];
int ret;
if (vale_mkname(vpname, ctx) < 0)
return -1;
printf("Testing NETMAP_REQ_VALE_POLLING_ENABLE on '%s'\n", vpname);
nmreq_hdr_init(&hdr, vpname);
hdr.nr_reqtype = NETMAP_REQ_VALE_POLLING_ENABLE;
hdr.nr_body = (uintptr_t)&req;
memset(&req, 0, sizeof(req));
req.nr_mode = ctx->nr_mode;
req.nr_first_cpu_id = ctx->nr_first_cpu_id;
req.nr_num_polling_cpus = ctx->nr_num_polling_cpus;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, VALE_POLLING_ENABLE)");
return ret;
}
return (req.nr_mode == ctx->nr_mode &&
req.nr_first_cpu_id == ctx->nr_first_cpu_id &&
req.nr_num_polling_cpus == ctx->nr_num_polling_cpus)
? 0
: -1;
}
/* NETMAP_REQ_VALE_POLLING_DISABLE */
static int
vale_polling_disable(struct TestContext *ctx)
{
struct nmreq_vale_polling req;
struct nmreq_header hdr;
char vpname[NM_IFNAMSZ];
int ret;
if (vale_mkname(vpname, ctx) < 0)
return -1;
printf("Testing NETMAP_REQ_VALE_POLLING_DISABLE on '%s'\n", vpname);
nmreq_hdr_init(&hdr, vpname);
hdr.nr_reqtype = NETMAP_REQ_VALE_POLLING_DISABLE;
hdr.nr_body = (uintptr_t)&req;
memset(&req, 0, sizeof(req));
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, VALE_POLLING_DISABLE)");
return ret;
}
return 0;
}
static int
vale_polling_enable_disable(struct TestContext *ctx)
{
int ret = 0;
if ((ret = vale_attach(ctx)) != 0) {
return ret;
}
ctx->nr_mode = NETMAP_POLLING_MODE_SINGLE_CPU;
ctx->nr_num_polling_cpus = 1;
ctx->nr_first_cpu_id = 0;
if ((ret = vale_polling_enable(ctx))) {
vale_detach(ctx);
#ifdef __FreeBSD__
/* NETMAP_REQ_VALE_POLLING_DISABLE is disabled on FreeBSD,
* because it is currently broken. We are happy to see that
* it fails. */
return 0;
#else
return ret;
#endif
}
if ((ret = vale_polling_disable(ctx))) {
vale_detach(ctx);
return ret;
}
return vale_detach(ctx);
}
static void
push_option(struct nmreq_option *opt, struct TestContext *ctx)
{
opt->nro_next = (uintptr_t)ctx->nr_opt;
ctx->nr_opt = opt;
}
static void
clear_options(struct TestContext *ctx)
{
ctx->nr_opt = NULL;
}
static int
checkoption(struct nmreq_option *opt, struct nmreq_option *exp)
{
if (opt->nro_next != exp->nro_next) {
printf("nro_next %p expected %p\n",
(void *)(uintptr_t)opt->nro_next,
(void *)(uintptr_t)exp->nro_next);
return -1;
}
if (opt->nro_reqtype != exp->nro_reqtype) {
printf("nro_reqtype %u expected %u\n", opt->nro_reqtype,
exp->nro_reqtype);
return -1;
}
if (opt->nro_status != exp->nro_status) {
printf("nro_status %u expected %u\n", opt->nro_status,
exp->nro_status);
return -1;
}
return 0;
}
static int
unsupported_option(struct TestContext *ctx)
{
struct nmreq_option opt, save;
printf("Testing unsupported option on %s\n", ctx->ifname_ext);
memset(&opt, 0, sizeof(opt));
opt.nro_reqtype = 1234;
push_option(&opt, ctx);
save = opt;
if (port_register_hwall(ctx) >= 0)
return -1;
clear_options(ctx);
save.nro_status = EOPNOTSUPP;
return checkoption(&opt, &save);
}
static int
infinite_options(struct TestContext *ctx)
{
struct nmreq_option opt;
printf("Testing infinite list of options on %s (invalid options)\n", ctx->ifname_ext);
memset(&opt, 0, sizeof(opt));
opt.nro_reqtype = NETMAP_REQ_OPT_MAX + 1;
push_option(&opt, ctx);
opt.nro_next = (uintptr_t)&opt;
if (port_register_hwall(ctx) >= 0)
return -1;
clear_options(ctx);
return (errno == EMSGSIZE ? 0 : -1);
}
static int
infinite_options2(struct TestContext *ctx)
{
struct nmreq_option opt;
printf("Testing infinite list of options on %s (valid options)\n", ctx->ifname_ext);
memset(&opt, 0, sizeof(opt));
opt.nro_reqtype = NETMAP_REQ_OPT_OFFSETS;
push_option(&opt, ctx);
opt.nro_next = (uintptr_t)&opt;
if (port_register_hwall(ctx) >= 0)
return -1;
clear_options(ctx);
return (errno == EINVAL ? 0 : -1);
}
#ifdef CONFIG_NETMAP_EXTMEM
int
change_param(const char *pname, unsigned long newv, unsigned long *poldv)
{
#ifdef __linux__
char param[256] = "/sys/module/netmap/parameters/";
unsigned long oldv;
FILE *f;
strncat(param, pname, sizeof(param) - 1);
f = fopen(param, "r+");
if (f == NULL) {
perror(param);
return -1;
}
if (fscanf(f, "%ld", &oldv) != 1) {
perror(param);
fclose(f);
return -1;
}
if (poldv)
*poldv = oldv;
rewind(f);
if (fprintf(f, "%ld\n", newv) < 0) {
perror(param);
fclose(f);
return -1;
}
fclose(f);
printf("change_param: %s: %ld -> %ld\n", pname, oldv, newv);
#endif /* __linux__ */
return 0;
}
static int
push_extmem_option(struct TestContext *ctx, const struct nmreq_pools_info *pi,
struct nmreq_opt_extmem *e)
{
void *addr;
addr = mmap(NULL, pi->nr_memsize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (addr == MAP_FAILED) {
perror("mmap");
return -1;
}
memset(e, 0, sizeof(*e));
e->nro_opt.nro_reqtype = NETMAP_REQ_OPT_EXTMEM;
e->nro_info = *pi;
e->nro_usrptr = (uintptr_t)addr;
push_option(&e->nro_opt, ctx);
return 0;
}
static int
pop_extmem_option(struct TestContext *ctx, struct nmreq_opt_extmem *exp)
{
struct nmreq_opt_extmem *e;
int ret;
e = (struct nmreq_opt_extmem *)(uintptr_t)ctx->nr_opt;
ctx->nr_opt = (struct nmreq_option *)(uintptr_t)ctx->nr_opt->nro_next;
if ((ret = checkoption(&e->nro_opt, &exp->nro_opt))) {
return ret;
}
if (e->nro_usrptr != exp->nro_usrptr) {
printf("usrptr %" PRIu64 " expected %" PRIu64 "\n",
e->nro_usrptr, exp->nro_usrptr);
return -1;
}
if (e->nro_info.nr_memsize != exp->nro_info.nr_memsize) {
printf("memsize %" PRIu64 " expected %" PRIu64 "\n",
e->nro_info.nr_memsize, exp->nro_info.nr_memsize);
return -1;
}
if ((ret = munmap((void *)(uintptr_t)e->nro_usrptr,
e->nro_info.nr_memsize)))
return ret;
return 0;
}
static int
_extmem_option(struct TestContext *ctx,
const struct nmreq_pools_info *pi)
{
struct nmreq_opt_extmem e, save;
int ret;
if ((ret = push_extmem_option(ctx, pi, &e)) < 0)
return ret;
save = e;
strncpy(ctx->ifname_ext, "vale0:0", sizeof(ctx->ifname_ext));
ctx->nr_tx_slots = 16;
ctx->nr_rx_slots = 16;
if ((ret = port_register_hwall(ctx)))
return ret;
ret = pop_extmem_option(ctx, &save);
return ret;
}
static size_t
pools_info_min_memsize(const struct nmreq_pools_info *pi)
{
size_t tot = 0;
tot += pi->nr_if_pool_objtotal * pi->nr_if_pool_objsize;
tot += pi->nr_ring_pool_objtotal * pi->nr_ring_pool_objsize;
tot += pi->nr_buf_pool_objtotal * pi->nr_buf_pool_objsize;
return tot;
}
/*
* Fill the specification of a netmap memory allocator to be
* used with the 'struct nmreq_opt_extmem' option. Arbitrary
* values are used for the parameters, but with enough netmap
* rings, netmap ifs, and buffers to support a VALE port.
*/
static void
pools_info_fill(struct nmreq_pools_info *pi)
{
pi->nr_if_pool_objtotal = 2;
pi->nr_if_pool_objsize = 1024;
pi->nr_ring_pool_objtotal = 64;
pi->nr_ring_pool_objsize = 512;
pi->nr_buf_pool_objtotal = 4096;
pi->nr_buf_pool_objsize = 2048;
pi->nr_memsize = pools_info_min_memsize(pi);
}
static int
extmem_option(struct TestContext *ctx)
{
struct nmreq_pools_info pools_info;
pools_info_fill(&pools_info);
printf("Testing extmem option on vale0:0\n");
return _extmem_option(ctx, &pools_info);
}
static int
bad_extmem_option(struct TestContext *ctx)
{
struct nmreq_pools_info pools_info;
printf("Testing bad extmem option on vale0:0\n");
pools_info_fill(&pools_info);
/* Request a large ring size, to make sure that the kernel
* rejects our request. */
pools_info.nr_ring_pool_objsize = (1 << 20);
return _extmem_option(ctx, &pools_info) < 0 ? 0 : -1;
}
static int
duplicate_extmem_options(struct TestContext *ctx)
{
struct nmreq_opt_extmem e1, save1, e2, save2;
struct nmreq_pools_info pools_info;
int ret;
printf("Testing duplicate extmem option on vale0:0\n");
pools_info_fill(&pools_info);
if ((ret = push_extmem_option(ctx, &pools_info, &e1)) < 0)
return ret;
if ((ret = push_extmem_option(ctx, &pools_info, &e2)) < 0) {
clear_options(ctx);
return ret;
}
save1 = e1;
save2 = e2;
strncpy(ctx->ifname_ext, "vale0:0", sizeof(ctx->ifname_ext));
ctx->nr_tx_slots = 16;
ctx->nr_rx_slots = 16;
ret = port_register_hwall(ctx);
if (ret >= 0) {
printf("duplicate option not detected\n");
return -1;
}
save2.nro_opt.nro_status = EINVAL;
if ((ret = pop_extmem_option(ctx, &save2)))
return ret;
save1.nro_opt.nro_status = EINVAL;
if ((ret = pop_extmem_option(ctx, &save1)))
return ret;
return 0;
}
#endif /* CONFIG_NETMAP_EXTMEM */
static int
push_csb_option(struct TestContext *ctx, struct nmreq_opt_csb *opt)
{
size_t csb_size;
int num_entries;
int ret;
ctx->nr_flags |= NR_EXCLUSIVE;
/* Get port info in order to use num_registered_rings(). */
ret = port_info_get(ctx);
if (ret != 0) {
return ret;
}
num_entries = num_registered_rings(ctx);
csb_size = (sizeof(struct nm_csb_atok) + sizeof(struct nm_csb_ktoa)) *
num_entries;
assert(csb_size > 0);
if (ctx->csb) {
free(ctx->csb);
}
ret = posix_memalign(&ctx->csb, sizeof(struct nm_csb_atok), csb_size);
if (ret != 0) {
printf("Failed to allocate CSB memory\n");
exit(EXIT_FAILURE);
}
memset(opt, 0, sizeof(*opt));
opt->nro_opt.nro_reqtype = NETMAP_REQ_OPT_CSB;
opt->csb_atok = (uintptr_t)ctx->csb;
opt->csb_ktoa = (uintptr_t)(((uint8_t *)ctx->csb) +
sizeof(struct nm_csb_atok) * num_entries);
printf("Pushing option NETMAP_REQ_OPT_CSB\n");
push_option(&opt->nro_opt, ctx);
return 0;
}
static int
csb_mode(struct TestContext *ctx)
{
struct nmreq_opt_csb opt;
int ret;
ret = push_csb_option(ctx, &opt);
if (ret != 0) {
return ret;
}
ret = port_register_hwall(ctx);
clear_options(ctx);
return ret;
}
static int
csb_mode_invalid_memory(struct TestContext *ctx)
{
struct nmreq_opt_csb opt;
int ret;
memset(&opt, 0, sizeof(opt));
opt.nro_opt.nro_reqtype = NETMAP_REQ_OPT_CSB;
opt.csb_atok = (uintptr_t)0x10;
opt.csb_ktoa = (uintptr_t)0x800;
push_option(&opt.nro_opt, ctx);
ctx->nr_flags = NR_EXCLUSIVE;
ret = port_register_hwall(ctx);
clear_options(ctx);
return (ret < 0) ? 0 : -1;
}
static int
sync_kloop_stop(struct TestContext *ctx)
{
struct nmreq_header hdr;
int ret;
printf("Testing NETMAP_REQ_SYNC_KLOOP_STOP on '%s'\n", ctx->ifname_ext);
nmreq_hdr_init(&hdr, ctx->ifname_ext);
hdr.nr_reqtype = NETMAP_REQ_SYNC_KLOOP_STOP;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, SYNC_KLOOP_STOP)");
}
return ret;
}
static void *
sync_kloop_worker(void *opaque)
{
struct TestContext *ctx = opaque;
struct nmreq_sync_kloop_start req;
struct nmreq_header hdr;
int ret;
printf("Testing NETMAP_REQ_SYNC_KLOOP_START on '%s'\n", ctx->ifname_ext);
nmreq_hdr_init(&hdr, ctx->ifname_ext);
hdr.nr_reqtype = NETMAP_REQ_SYNC_KLOOP_START;
hdr.nr_body = (uintptr_t)&req;
hdr.nr_options = (uintptr_t)ctx->nr_opt;
memset(&req, 0, sizeof(req));
req.sleep_us = 500;
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, SYNC_KLOOP_START)");
}
if (ctx->sem) {
sem_post(ctx->sem);
}
pthread_exit(ret ? (void *)THRET_FAILURE : (void *)THRET_SUCCESS);
}
static int
sync_kloop_start_stop(struct TestContext *ctx)
{
pthread_t th;
void *thret = THRET_FAILURE;
int ret;
ret = pthread_create(&th, NULL, sync_kloop_worker, ctx);
if (ret != 0) {
printf("pthread_create(kloop): %s\n", strerror(ret));
return -1;
}
ret = sync_kloop_stop(ctx);
if (ret != 0) {
return ret;
}
ret = pthread_join(th, &thret);
if (ret != 0) {
printf("pthread_join(kloop): %s\n", strerror(ret));
}
return thret == THRET_SUCCESS ? 0 : -1;
}
static int
sync_kloop(struct TestContext *ctx)
{
int ret;
ret = csb_mode(ctx);
if (ret != 0) {
return ret;
}
return sync_kloop_start_stop(ctx);
}
static int
sync_kloop_eventfds(struct TestContext *ctx)
{
struct nmreq_opt_sync_kloop_eventfds *evopt = NULL;
struct nmreq_opt_sync_kloop_mode modeopt;
struct nmreq_option evsave;
int num_entries;
size_t opt_size;
int ret, i;
memset(&modeopt, 0, sizeof(modeopt));
modeopt.nro_opt.nro_reqtype = NETMAP_REQ_OPT_SYNC_KLOOP_MODE;
modeopt.mode = ctx->sync_kloop_mode;
push_option(&modeopt.nro_opt, ctx);
num_entries = num_registered_rings(ctx);
opt_size = sizeof(*evopt) + num_entries * sizeof(evopt->eventfds[0]);
evopt = calloc(1, opt_size);
evopt->nro_opt.nro_next = 0;
evopt->nro_opt.nro_reqtype = NETMAP_REQ_OPT_SYNC_KLOOP_EVENTFDS;
evopt->nro_opt.nro_status = 0;
evopt->nro_opt.nro_size = opt_size;
for (i = 0; i < num_entries; i++) {
int efd = eventfd(0, 0);
evopt->eventfds[i].ioeventfd = efd;
efd = eventfd(0, 0);
evopt->eventfds[i].irqfd = efd;
}
push_option(&evopt->nro_opt, ctx);
evsave = evopt->nro_opt;
ret = sync_kloop_start_stop(ctx);
if (ret != 0) {
free(evopt);
clear_options(ctx);
return ret;
}
#ifdef __linux__
evsave.nro_status = 0;
#else /* !__linux__ */
evsave.nro_status = EOPNOTSUPP;
#endif /* !__linux__ */
ret = checkoption(&evopt->nro_opt, &evsave);
free(evopt);
clear_options(ctx);
return ret;
}
static int
sync_kloop_eventfds_all_mode(struct TestContext *ctx,
uint32_t sync_kloop_mode)
{
int ret;
ret = csb_mode(ctx);
if (ret != 0) {
return ret;
}
ctx->sync_kloop_mode = sync_kloop_mode;
return sync_kloop_eventfds(ctx);
}
static int
sync_kloop_eventfds_all(struct TestContext *ctx)
{
return sync_kloop_eventfds_all_mode(ctx, 0);
}
static int
sync_kloop_eventfds_all_tx(struct TestContext *ctx)
{
struct nmreq_opt_csb opt;
int ret;
ret = push_csb_option(ctx, &opt);
if (ret != 0) {
return ret;
}
ret = port_register_hwall_tx(ctx);
if (ret != 0) {
return ret;
}
clear_options(ctx);
return sync_kloop_eventfds(ctx);
}
static int
sync_kloop_eventfds_all_direct(struct TestContext *ctx)
{
return sync_kloop_eventfds_all_mode(ctx,
NM_OPT_SYNC_KLOOP_DIRECT_TX | NM_OPT_SYNC_KLOOP_DIRECT_RX);
}
static int
sync_kloop_eventfds_all_direct_tx(struct TestContext *ctx)
{
return sync_kloop_eventfds_all_mode(ctx,
NM_OPT_SYNC_KLOOP_DIRECT_TX);
}
static int
sync_kloop_eventfds_all_direct_rx(struct TestContext *ctx)
{
return sync_kloop_eventfds_all_mode(ctx,
NM_OPT_SYNC_KLOOP_DIRECT_RX);
}
static int
sync_kloop_nocsb(struct TestContext *ctx)
{
int ret;
ret = port_register_hwall(ctx);
if (ret != 0) {
return ret;
}
/* Sync kloop must fail because we did not use
* NETMAP_REQ_CSB_ENABLE. */
return sync_kloop_start_stop(ctx) != 0 ? 0 : -1;
}
static int
csb_enable(struct TestContext *ctx)
{
struct nmreq_option saveopt;
struct nmreq_opt_csb opt;
struct nmreq_header hdr;
int ret;
ret = push_csb_option(ctx, &opt);
if (ret != 0) {
return ret;
}
saveopt = opt.nro_opt;
saveopt.nro_status = 0;
nmreq_hdr_init(&hdr, ctx->ifname_ext);
hdr.nr_reqtype = NETMAP_REQ_CSB_ENABLE;
hdr.nr_options = (uintptr_t)ctx->nr_opt;
hdr.nr_body = (uintptr_t)NULL;
printf("Testing NETMAP_REQ_CSB_ENABLE on '%s'\n", ctx->ifname_ext);
ret = ioctl(ctx->fd, NIOCCTRL, &hdr);
if (ret != 0) {
perror("ioctl(/dev/netmap, NIOCCTRL, CSB_ENABLE)");
return ret;
}
ret = checkoption(&opt.nro_opt, &saveopt);
clear_options(ctx);
return ret;
}
static int
sync_kloop_csb_enable(struct TestContext *ctx)
{
int ret;
ctx->nr_flags |= NR_EXCLUSIVE;
ret = port_register_hwall(ctx);
if (ret != 0) {
return ret;
}
ret = csb_enable(ctx);
if (ret != 0) {
return ret;
}
return sync_kloop_start_stop(ctx);
}
static int
sync_kloop_conflict(struct TestContext *ctx)
{
struct nmreq_opt_csb opt;
pthread_t th1, th2;
void *thret1 = THRET_FAILURE, *thret2 = THRET_FAILURE;
struct timespec to;
sem_t sem;
int err = 0;
int ret;
ret = push_csb_option(ctx, &opt);
if (ret != 0) {
return ret;
}
ret = port_register_hwall(ctx);
if (ret != 0) {
return ret;
}
clear_options(ctx);
ret = sem_init(&sem, 0, 0);
if (ret != 0) {
printf("sem_init() failed: %s\n", strerror(ret));
return ret;
}
ctx->sem = &sem;
ret = pthread_create(&th1, NULL, sync_kloop_worker, ctx);
err |= ret;
if (ret != 0) {
printf("pthread_create(kloop1): %s\n", strerror(ret));
}
ret = pthread_create(&th2, NULL, sync_kloop_worker, ctx);
err |= ret;
if (ret != 0) {
printf("pthread_create(kloop2): %s\n", strerror(ret));
}
/* Wait for one of the two threads to fail to start the kloop, to
* avoid a race condition where th1 starts the loop and stops,
* and after that th2 starts the loop successfully. */
clock_gettime(CLOCK_REALTIME, &to);
to.tv_sec += 2;
ret = sem_timedwait(&sem, &to);
err |= ret;
if (ret != 0) {
printf("sem_timedwait() failed: %s\n", strerror(errno));
}
err |= sync_kloop_stop(ctx);
ret = pthread_join(th1, &thret1);
err |= ret;
if (ret != 0) {
printf("pthread_join(kloop1): %s\n", strerror(ret));
}
ret = pthread_join(th2, &thret2);
err |= ret;
if (ret != 0) {
printf("pthread_join(kloop2): %s %d\n", strerror(ret), ret);
}
sem_destroy(&sem);
ctx->sem = NULL;
if (err) {
return err;
}
/* Check that one of the two failed, while the other one succeeded. */
return ((thret1 == THRET_SUCCESS && thret2 == THRET_FAILURE) ||
(thret1 == THRET_FAILURE && thret2 == THRET_SUCCESS))
? 0
: -1;
}
static int
sync_kloop_eventfds_mismatch(struct TestContext *ctx)
{
struct nmreq_opt_csb opt;
int ret;
ret = push_csb_option(ctx, &opt);
if (ret != 0) {
return ret;
}
ret = port_register_hwall_rx(ctx);
if (ret != 0) {
return ret;
}
clear_options(ctx);
/* Deceive num_registered_rings() to trigger a failure of
* sync_kloop_eventfds(). The latter will think that all the
* rings were registered, and allocate the wrong number of
* eventfds. */
ctx->nr_flags &= ~NR_RX_RINGS_ONLY;
return (sync_kloop_eventfds(ctx) != 0) ? 0 : -1;
}
static int
null_port(struct TestContext *ctx)
{
int ret;
ctx->nr_mem_id = 1;
ctx->nr_mode = NR_REG_NULL;
ctx->nr_tx_rings = 10;
ctx->nr_rx_rings = 5;
ctx->nr_tx_slots = 256;
ctx->nr_rx_slots = 100;
ret = port_register(ctx);
if (ret != 0) {
return ret;
}
return 0;
}
static int
null_port_all_zero(struct TestContext *ctx)
{
int ret;
ctx->nr_mem_id = 1;
ctx->nr_mode = NR_REG_NULL;
ctx->nr_tx_rings = 0;
ctx->nr_rx_rings = 0;
ctx->nr_tx_slots = 0;
ctx->nr_rx_slots = 0;
ret = port_register(ctx);
if (ret != 0) {
return ret;
}
return 0;
}
static int
null_port_sync(struct TestContext *ctx)
{
int ret;
ctx->nr_mem_id = 1;
ctx->nr_mode = NR_REG_NULL;
ctx->nr_tx_rings = 10;
ctx->nr_rx_rings = 5;
ctx->nr_tx_slots = 256;
ctx->nr_rx_slots = 100;
ret = port_register(ctx);
if (ret != 0) {
return ret;
}
ret = ioctl(ctx->fd, NIOCTXSYNC, 0);
if (ret != 0) {
return ret;
}
return 0;
}
struct nmreq_parse_test {
const char *ifname;
const char *exp_port;
const char *exp_suff;
int exp_error;
uint32_t exp_mode;
uint16_t exp_ringid;
uint64_t exp_flags;
};
static struct nmreq_parse_test nmreq_parse_tests[] = {
/* port spec is the input. The expected results are as follows:
* - port: what should go into hdr.nr_name
* - suff: the trailing part of the input after parsing (NULL means equal to port spec)
* - err: the expected return value, interpreted as follows
* err > 0 => nmreq_header_parse should fail with the given error
* err < 0 => nrmeq_header_parse should succeed, but nmreq_register_decode should
* fail with error |err|
* err = 0 => should succeed
* - mode, ringid flags: what should go into the corresponding nr_* fields in the
* nmreq_register struct in case of success
*/
/*port spec*/ /*port*/ /*suff*/ /*err*/ /*mode*/ /*ringid*/ /*flags*/
{ "netmap:eth0", "eth0", "", 0, NR_REG_ALL_NIC, 0, 0 },
{ "netmap:eth0-1", "eth0", "", 0, NR_REG_ONE_NIC, 1, 0 },
{ "netmap:eth0-", "eth0", "-", -EINVAL,0, 0, 0 },
{ "netmap:eth0/x", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_EXCLUSIVE },
{ "netmap:eth0/z", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_ZCOPY_MON },
{ "netmap:eth0/r", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_MONITOR_RX },
{ "netmap:eth0/t", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_MONITOR_TX },
{ "netmap:eth0-2/Tx", "eth0", "", 0, NR_REG_ONE_NIC, 2, NR_TX_RINGS_ONLY|NR_EXCLUSIVE },
{ "netmap:eth0*", "eth0", "", 0, NR_REG_NIC_SW, 0, 0 },
{ "netmap:eth0^", "eth0", "", 0, NR_REG_SW, 0, 0 },
{ "netmap:eth0@2", "eth0", "", 0, NR_REG_ALL_NIC, 0, 0 },
{ "netmap:eth0@2/R", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_RX_RINGS_ONLY },
{ "netmap:eth0@netmap:lo/R", "eth0", "@netmap:lo/R", 0, NR_REG_ALL_NIC, 0, 0 },
{ "netmap:eth0/R@xxx", "eth0", "@xxx", 0, NR_REG_ALL_NIC, 0, NR_RX_RINGS_ONLY },
{ "netmap:eth0@2/R@2", "eth0", "", 0, NR_REG_ALL_NIC, 0, NR_RX_RINGS_ONLY },
{ "netmap:eth0@2/R@3", "eth0", "@2/R@3", -EINVAL,0, 0, 0 },
{ "netmap:eth0@", "eth0", "@", -EINVAL,0, 0, 0 },
{ "netmap:", "", NULL, EINVAL, 0, 0, 0 },
{ "netmap:^", "", NULL, EINVAL, 0, 0, 0 },
{ "netmap:{", "", NULL, EINVAL, 0, 0, 0 },
{ "netmap:vale0:0", NULL, NULL, EINVAL, 0, 0, 0 },
{ "eth0", NULL, NULL, EINVAL, 0, 0, 0 },
{ "vale0:0", "vale0:0", "", 0, NR_REG_ALL_NIC, 0, 0 },
{ "vale:0", "vale:0", "", 0, NR_REG_ALL_NIC, 0, 0 },
{ "valeXXX:YYY", "valeXXX:YYY", "", 0, NR_REG_ALL_NIC, 0, 0 },
{ "valeXXX:YYY-4", "valeXXX:YYY", "", 0, NR_REG_ONE_NIC, 4, 0 },
{ "netmapXXX:eth0", NULL, NULL, EINVAL, 0, 0, 0 },
{ "netmap:14", "14", "", 0, NR_REG_ALL_NIC, 0, 0 },
{ "netmap:eth0&", NULL, NULL, EINVAL, 0, 0, 0 },
{ "netmap:pipe{0", "pipe{0", "", 0, NR_REG_ALL_NIC, 0, 0 },
{ "netmap:pipe{in", "pipe{in", "", 0, NR_REG_ALL_NIC, 0, 0 },
{ "netmap:pipe{in-7", "pipe{in", "", 0, NR_REG_ONE_NIC, 7, 0 },
{ "vale0:0{0", "vale0:0{0", "", 0, NR_REG_ALL_NIC, 0, 0 },
{ "netmap:pipe{1}2", NULL, NULL, EINVAL, 0, 0, 0 },
{ "vale0:0@opt", "vale0:0", "@opt", 0, NR_REG_ALL_NIC, 0, 0 },
{ "vale0:0/Tx@opt", "vale0:0", "@opt", 0, NR_REG_ALL_NIC, 0, NR_TX_RINGS_ONLY|NR_EXCLUSIVE },
{ "vale0:0-3@opt", "vale0:0", "@opt", 0, NR_REG_ONE_NIC, 3, 0 },
{ "vale0:0@", "vale0:0", "@", -EINVAL,0, 0, 0 },
{ "", NULL, NULL, EINVAL, 0, 0, 0 },
{ NULL, NULL, NULL, 0, 0, 0, 0 },
};
static void
randomize(void *dst, size_t n)
{
size_t i;
char *dst_ = dst;
for (i = 0; i < n; i++)
dst_[i] = (char)random();
}
static int
nmreq_hdr_parsing(struct TestContext *ctx,
struct nmreq_parse_test *t,
struct nmreq_header *hdr)
{
const char *save;
struct nmreq_header orig_hdr;
save = ctx->ifparse = t->ifname;
orig_hdr = *hdr;
printf("nmreq_header: \"%s\"\n", ctx->ifparse);
if (nmreq_header_decode(&ctx->ifparse, hdr, ctx->nmctx) < 0) {
if (t->exp_error > 0) {
if (errno != t->exp_error) {
printf("!!! got errno=%d, want %d\n",
errno, t->exp_error);
return -1;
}
if (ctx->ifparse != save) {
printf("!!! parse error, but first arg changed\n");
return -1;
}
if (memcmp(&orig_hdr, hdr, sizeof(*hdr))) {
printf("!!! parse error, but header changed\n");
return -1;
}
return 0;
}
printf ("!!! nmreq_header_decode was expected to succeed, but it failed with error %d\n", errno);
return -1;
}
if (t->exp_error > 0) {
printf("!!! nmreq_header_decode returns 0, but error %d was expected\n", t->exp_error);
return -1;
}
if (strcmp(t->exp_port, hdr->nr_name) != 0) {
printf("!!! got '%s', want '%s'\n", hdr->nr_name, t->exp_port);
return -1;
}
if (hdr->nr_reqtype != orig_hdr.nr_reqtype ||
hdr->nr_options != orig_hdr.nr_options ||
hdr->nr_body != orig_hdr.nr_body) {
printf("!!! some fields of the nmreq_header where changed unexpectedly\n");
return -1;
}
return 0;
}
static int
nmreq_reg_parsing(struct TestContext *ctx,
struct nmreq_parse_test *t,
struct nmreq_register *reg)
{
const char *save;
struct nmreq_register orig_reg;
save = ctx->ifparse;
orig_reg = *reg;
printf("nmreq_register: \"%s\"\n", ctx->ifparse);
if (nmreq_register_decode(&ctx->ifparse, reg, ctx->nmctx) < 0) {
if (t->exp_error < 0) {
if (errno != -t->exp_error) {
printf("!!! got errno=%d, want %d\n",
errno, -t->exp_error);
return -1;
}
if (ctx->ifparse != save) {
printf("!!! parse error, but first arg changed\n");
return -1;
}
if (memcmp(&orig_reg, reg, sizeof(*reg))) {
printf("!!! parse error, but nmreq_register changed\n");
return -1;
}
return 0;
}
printf ("!!! parse failed but it should have succeeded\n");
return -1;
}
if (t->exp_error < 0) {
printf("!!! nmreq_register_decode returns 0, but error %d was expected\n", -t->exp_error);
return -1;
}
if (reg->nr_mode != t->exp_mode) {
printf("!!! got nr_mode '%d', want '%d'\n", reg->nr_mode, t->exp_mode);
return -1;
}
if (reg->nr_ringid != t->exp_ringid) {
printf("!!! got nr_ringid '%d', want '%d'\n", reg->nr_ringid, t->exp_ringid);
return -1;
}
if (reg->nr_flags != t->exp_flags) {
printf("!!! got nm_flags '%llx', want '%llx\n", (unsigned long long)reg->nr_flags,
(unsigned long long)t->exp_flags);
return -1;
}
if (reg->nr_offset != orig_reg.nr_offset ||
reg->nr_memsize != orig_reg.nr_memsize ||
reg->nr_tx_slots != orig_reg.nr_tx_slots ||
reg->nr_rx_slots != orig_reg.nr_rx_slots ||
reg->nr_tx_rings != orig_reg.nr_tx_rings ||
reg->nr_rx_rings != orig_reg.nr_rx_rings ||
reg->nr_extra_bufs != orig_reg.nr_extra_bufs)
{
printf("!!! some fields of the nmreq_register where changed unexpectedly\n");
return -1;
}
return 0;
}
static void
nmctx_parsing_error(struct nmctx *ctx, const char *msg)
{
(void)ctx;
printf(" got message: %s\n", msg);
}
static int
nmreq_parsing(struct TestContext *ctx)
{
struct nmreq_parse_test *t;
struct nmreq_header hdr;
struct nmreq_register reg;
struct nmctx test_nmctx, *nmctx;
int ret = 0;
nmctx = nmctx_get();
if (nmctx == NULL) {
printf("Failed to acquire nmctx: %s", strerror(errno));
return -1;
}
test_nmctx = *nmctx;
test_nmctx.error = nmctx_parsing_error;
ctx->nmctx = &test_nmctx;
for (t = nmreq_parse_tests; t->ifname != NULL; t++) {
const char *exp_suff = t->exp_suff != NULL ?
t->exp_suff : t->ifname;
randomize(&hdr, sizeof(hdr));
randomize(&reg, sizeof(reg));
reg.nr_mem_id = 0;
if (nmreq_hdr_parsing(ctx, t, &hdr) < 0) {
ret = -1;
} else if (t->exp_error <= 0 && nmreq_reg_parsing(ctx, t, &reg) < 0) {
ret = -1;
}
if (strcmp(ctx->ifparse, exp_suff) != 0) {
printf("!!! string suffix after parse is '%s', but it should be '%s'\n",
ctx->ifparse, exp_suff);
ret = -1;
}
}
ctx->nmctx = NULL;
return ret;
}
static int
binarycomp(struct TestContext *ctx)
{
#define ckroff(f, o) do {\
if (offsetof(struct netmap_ring, f) != (o)) {\
printf("offset of netmap_ring.%s is %zd, but it should be %d",\
#f, offsetof(struct netmap_ring, f), (o));\
return -1;\
}\
} while (0)
(void)ctx;
ckroff(buf_ofs, 0);
ckroff(num_slots, 8);
ckroff(nr_buf_size, 12);
ckroff(ringid, 16);
ckroff(dir, 18);
ckroff(head, 20);
ckroff(cur, 24);
ckroff(tail, 28);
ckroff(flags, 32);
ckroff(ts, 40);
ckroff(offset_mask, 56);
ckroff(buf_align, 64);
ckroff(sem, 128);
ckroff(slot, 256);
return 0;
}
static void
usage(const char *prog)
{
printf("%s -i IFNAME\n"
"[-j TEST_NUM1[-[TEST_NUM2]] | -[TEST_NUM_2]]\n"
"[-l (list test cases)]\n",
prog);
}
struct mytest {
testfunc_t test;
const char *name;
};
#define decltest(f) \
{ \
.test = f, .name = #f \
}
static struct mytest tests[] = {
decltest(port_info_get),
decltest(port_register_hwall_host),
decltest(port_register_hwall),
decltest(port_register_hostall),
decltest(port_register_single_hw_pair),
decltest(port_register_single_host_pair),
decltest(port_register_hostall_many),
decltest(vale_attach_detach),
decltest(vale_attach_detach_host_rings),
decltest(vale_ephemeral_port_hdr_manipulation),
decltest(vale_persistent_port),
decltest(pools_info_get_and_register),
decltest(pools_info_get_empty_ifname),
decltest(pipe_master),
decltest(pipe_slave),
decltest(pipe_port_info_get),
decltest(pipe_pools_info_get),
decltest(vale_polling_enable_disable),
decltest(unsupported_option),
decltest(infinite_options),
decltest(infinite_options2),
#ifdef CONFIG_NETMAP_EXTMEM
decltest(extmem_option),
decltest(bad_extmem_option),
decltest(duplicate_extmem_options),
#endif /* CONFIG_NETMAP_EXTMEM */
decltest(csb_mode),
decltest(csb_mode_invalid_memory),
decltest(sync_kloop),
decltest(sync_kloop_eventfds_all),
decltest(sync_kloop_eventfds_all_tx),
decltest(sync_kloop_eventfds_all_direct),
decltest(sync_kloop_eventfds_all_direct_tx),
decltest(sync_kloop_eventfds_all_direct_rx),
decltest(sync_kloop_nocsb),
decltest(sync_kloop_csb_enable),
decltest(sync_kloop_conflict),
decltest(sync_kloop_eventfds_mismatch),
decltest(null_port),
decltest(null_port_all_zero),
decltest(null_port_sync),
decltest(legacy_regif_default),
decltest(legacy_regif_all_nic),
decltest(legacy_regif_12),
decltest(legacy_regif_sw),
decltest(legacy_regif_future),
decltest(legacy_regif_extra_bufs),
decltest(legacy_regif_extra_bufs_pipe),
decltest(legacy_regif_extra_bufs_pipe_vale),
decltest(nmreq_parsing),
decltest(binarycomp),
};
static void
context_cleanup(struct TestContext *ctx)
{
if (ctx->csb) {
free(ctx->csb);
ctx->csb = NULL;
}
close(ctx->fd);
ctx->fd = -1;
}
static int
parse_interval(const char *arg, int *j, int *k)
{
const char *scan = arg;
char *rest;
*j = 0;
*k = -1;
if (*scan == '-') {
scan++;
goto get_k;
}
if (!isdigit(*scan))
goto err;
*k = strtol(scan, &rest, 10);
*j = *k - 1;
scan = rest;
if (*scan == '-') {
*k = -1;
scan++;
}
get_k:
if (*scan == '\0')
return 0;
if (!isdigit(*scan))
goto err;
*k = strtol(scan, &rest, 10);
scan = rest;
if (!(*scan == '\0'))
goto err;
return 0;
err:
fprintf(stderr, "syntax error in '%s', must be num[-[num]] or -[num]\n", arg);
return -1;
}
#define ARGV_APPEND(_av, _ac, _x)\
do {\
assert((int)(_ac) < (int)(sizeof(_av)/sizeof((_av)[0])));\
(_av)[(_ac)++] = _x;\
} while (0)
static void
tap_cleanup(int signo)
{
const char *av[8];
int ac = 0;
(void)signo;
#ifdef __FreeBSD__
ARGV_APPEND(av, ac, "ifconfig");
ARGV_APPEND(av, ac, ctx_.ifname);
ARGV_APPEND(av, ac, "destroy");
#else
ARGV_APPEND(av, ac, "ip");
ARGV_APPEND(av, ac, "link");
ARGV_APPEND(av, ac, "del");
ARGV_APPEND(av, ac, ctx_.ifname);
#endif
ARGV_APPEND(av, ac, NULL);
if (exec_command(ac, av)) {
printf("Failed to destroy tap interface\n");
}
}
int
main(int argc, char **argv)
{
int create_tap = 1;
int num_tests;
int ret = 0;
int j = 0;
int k = -1;
int list = 0;
int opt;
int i;
#ifdef __FreeBSD__
PLAIN_REQUIRE_KERNEL_MODULE("if_tap", 0);
PLAIN_REQUIRE_KERNEL_MODULE("netmap", 0);
#endif
memset(&ctx_, 0, sizeof(ctx_));
{
struct timespec t;
int idx;
clock_gettime(CLOCK_REALTIME, &t);
srand((unsigned int)t.tv_nsec);
idx = rand() % 8000 + 100;
snprintf(ctx_.ifname, sizeof(ctx_.ifname), "tap%d", idx);
idx = rand() % 800 + 100;
snprintf(ctx_.bdgname, sizeof(ctx_.bdgname), "vale%d", idx);
}
while ((opt = getopt(argc, argv, "hi:j:l")) != -1) {
switch (opt) {
case 'h':
usage(argv[0]);
return 0;
case 'i':
strncpy(ctx_.ifname, optarg, sizeof(ctx_.ifname) - 1);
create_tap = 0;
break;
case 'j':
if (parse_interval(optarg, &j, &k) < 0) {
usage(argv[0]);
return -1;
}
break;
case 'l':
list = 1;
create_tap = 0;
break;
default:
printf(" Unrecognized option %c\n", opt);
usage(argv[0]);
return -1;
}
}
num_tests = sizeof(tests) / sizeof(tests[0]);
if (j < 0 || j >= num_tests || k > num_tests) {
fprintf(stderr, "Test interval %d-%d out of range (%d-%d)\n",
j + 1, k, 1, num_tests + 1);
return -1;
}
if (k < 0)
k = num_tests;
if (list) {
printf("Available tests:\n");
for (i = 0; i < num_tests; i++) {
printf("#%03d: %s\n", i + 1, tests[i].name);
}
return 0;
}
if (create_tap) {
struct sigaction sa;
const char *av[8];
int ac = 0;
#ifdef __FreeBSD__
ARGV_APPEND(av, ac, "ifconfig");
ARGV_APPEND(av, ac, ctx_.ifname);
ARGV_APPEND(av, ac, "create");
ARGV_APPEND(av, ac, "up");
#else
ARGV_APPEND(av, ac, "ip");
ARGV_APPEND(av, ac, "tuntap");
ARGV_APPEND(av, ac, "add");
ARGV_APPEND(av, ac, "mode");
ARGV_APPEND(av, ac, "tap");
ARGV_APPEND(av, ac, "name");
ARGV_APPEND(av, ac, ctx_.ifname);
#endif
ARGV_APPEND(av, ac, NULL);
if (exec_command(ac, av)) {
printf("Failed to create tap interface\n");
return -1;
}
sa.sa_handler = tap_cleanup;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
ret = sigaction(SIGINT, &sa, NULL);
if (ret) {
perror("sigaction(SIGINT)");
goto out;
}
ret = sigaction(SIGTERM, &sa, NULL);
if (ret) {
perror("sigaction(SIGTERM)");
goto out;
}
}
for (i = j; i < k; i++) {
struct TestContext ctxcopy;
int fd;
printf("==> Start of Test #%d [%s]\n", i + 1, tests[i].name);
fd = open("/dev/netmap", O_RDWR);
if (fd < 0) {
perror("open(/dev/netmap)");
ret = fd;
goto out;
}
memcpy(&ctxcopy, &ctx_, sizeof(ctxcopy));
ctxcopy.fd = fd;
memcpy(ctxcopy.ifname_ext, ctxcopy.ifname,
sizeof(ctxcopy.ifname));
ret = tests[i].test(&ctxcopy);
if (ret != 0) {
printf("Test #%d [%s] failed\n", i + 1, tests[i].name);
goto out;
}
printf("==> Test #%d [%s] successful\n", i + 1, tests[i].name);
context_cleanup(&ctxcopy);
}
out:
tap_cleanup(0);
return ret;
}