diff options
Diffstat (limited to 'tests/sys/netmap/ctrl-api-test.c')
-rw-r--r-- | tests/sys/netmap/ctrl-api-test.c | 2328 |
1 files changed, 2328 insertions, 0 deletions
diff --git a/tests/sys/netmap/ctrl-api-test.c b/tests/sys/netmap/ctrl-api-test.c new file mode 100644 index 000000000000..36c131980360 --- /dev/null +++ b/tests/sys/netmap/ctrl-api-test.c @@ -0,0 +1,2328 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + */ + +/* + * 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__ +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); +} + +#if 0 +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. */ + /* + * XXX: This doesn't fully close the race. th2 might fail to + * start executing since th1 can enter the kernel and hog the + * CPU on a single-CPU system until the semaphore timeout + * awakens this thread and it calls sync_kloop_stop. Once th1 + * exits the kernel, th2 can finally run and will then loop + * forever in the ioctl handler. + */ + 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; +} +#endif + +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 }, + { "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: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(®, 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, ®) < 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), +#if 0 + decltest(sync_kloop_conflict), +#endif + 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; + + 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; +} |