On Fri, Jan 10, 2020 at 01:00:49PM +0000, Tom Smyth wrote:
> Hi lads,
> 
> I have been doing some testing with tap(4) and openvpn (standard ssl )
> I have been using openvpn with tap and I have been trying with null
> encryption. null authentication,
> the performance of the tap interface  seems to be about 100-150Mb/s  on a 
> system
> which can give  3Gb/s-5Gb/s on ix(4) interfaces  in Bridge mode and
> 4-8Gb/s on tpmr mode
> I was wondering is there a sysctl setting that if modified would
> improve the tap interface performance.
> I have tried with tpmr(4) and  bridge(4)
> 
> is there a simple way  testing a tap(4) interface throughput /
> performance without Openvpn process
> 
> I can try mlvpn and wireguard
> but I would love if there was a trick where I can just test the tap(4)
> interface  with something like pair(4)...
> 
> ix0---bridge0--tap0---someprocess--tap1-bridge1--ix1
> or
> ix0--tpmr0--tap0--someprocess--tap1-tpmr1-ix1
> 
> is there a simple "someprocess" that would provide forwarding packets
> between tap0 and tap1 in userland
> so that any performance testing on tap(4) interfaces does not have the
> distractions of complex userland programs with encryption /
> encapsulation overheads
> 

I just wrote a simple tun/tap bridge for testing so here you go.
Compile it with 'cc -Wall -o tbridge tbridge.c -lpthread' and run it
with 'tbridge -k /dev/tun0 /dev/tun1' to wire tun0 and tun1 together.
You can select between, select(2), poll(2), kqueue(2) and pthreads as the
way on how to multiplex the reads.

For me the code triggers scheduler inefficencies and causes packets drops
on the output queue when there are multiple packet producers.
-- 
:wq Claudio

/*
 * Copyright (c) 2020 Claudio Jeker <clau...@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

volatile sig_atomic_t    quit;

static void
do_read(int in, int out)
{
        char buf[2048];
        ssize_t n, o;

        n = read(in, buf, sizeof(buf));
        if (n == -1)
                err(1, "read");
        o = write(out, buf, n);
        if (o == -1)
                err(1, "read");
        if (o != n)
                errx(1, "short write");
}

static void
do_poll(int fd[2])
{
        struct pollfd pfd[2];
        int n, i;

        while (quit == 0) {
                memset(pfd, 0, sizeof(pfd));
                pfd[0].fd = fd[0];
                pfd[0].events = POLLIN;

                pfd[1].fd = fd[1];
                pfd[1].events = POLLIN;

                n = poll(pfd, 2, INFTIM);
                if (n == -1)
                        err(1, "poll");
                if (n == 0)
                        errx(1, "poll: timeout");
                for (i = 0; i < 2; i++) {
                        if (pfd[i].revents & POLLIN)
                                do_read(fd[i], fd[(i + 1) & 0x1]);
                        else if (pfd[i].revents & (POLLHUP | POLLERR))
                                errx(1, "fd %d revents %x", i, pfd[i].revents);
                }
        }

}

static void
do_select(int fd[2])
{
        fd_set readfds;
        int n, i, maxfd = -1;

        while (quit == 0) {
                FD_ZERO(&readfds);
                for (i = 0; i < 2; i++) {
                        if (fd[i] > maxfd)
                                maxfd = fd[i];
                        FD_SET(fd[i], &readfds);
                }
                n = select(maxfd + 1, &readfds, NULL, NULL, NULL);
                if (n == -1)
                        err(1, "select");
                if (n == 0)
                        errx(1, "select: timeout");
                for (i = 0; i < 2; i++) {
                        if (FD_ISSET(fd[i], &readfds))
                                do_read(fd[i], fd[(i + 1) & 0x1]);
                }
        }
}

static void
do_kqueue(int fd[2])
{
        struct kevent kev[2];
        int kq, i, n;

        if ((kq = kqueue()) == -1)
                err(1, "kqueue");

        memset(kev, 0, sizeof(kev));
        for (i = 0; i < 2; i++) {
                EV_SET(&kev[i], fd[i], EVFILT_READ, EV_ADD | EV_ENABLE,
                    0, 0, (void *)(intptr_t)i);
        }
        if (kevent(kq, kev, 2, NULL, 0, NULL) == -1)
                err(1, "kevent register");

        while (quit == 0) {
                n = kevent(kq, NULL, 0, kev, 2, NULL);
                if (n == -1)
                        err(1, "kevent");
                if (n == 0)
                        errx(1, "kevent: timeout");
                for (i = 0; i < n; i++) {
                        if (kev[i].flags & EV_ERROR)
                                errc(1, kev[i].data, "kevent EV_ERROR");
                        if (kev[i].filter == EVFILT_READ) {
                                int r = (int)kev[i].udata;
                                do_read(fd[r], fd[(r + 1) & 0x1]);
                        }
                }
        }
}

static void *
run_thread(void *arg)
{
        int *fd = arg;

        while (quit == 0)
                do_read(fd[0], fd[1]);

        return NULL;
}

static void
do_thread(int fd[2])
{
        pthread_t tid;
        int ret;

        ret = pthread_create(&tid, NULL, run_thread, fd);
        if (ret)  {
                errc(1, ret, "pthread_create");
        }

        while (quit == 0)
                do_read(fd[1], fd[0]);
}

static void
sighdlr(int sig)
{
        quit = 1;
}

static __dead void
usage(void)
{
        fprintf(stderr, "tbridge -k | -p | -s | -t tapA tapB\n");
        exit(1);
}

int
main(int argc, char **argv)
{
        int fd[2];
        int ch = 0;
        int mode = 0;

        while ((ch = getopt(argc, argv, "kpst")) != -1) {
                switch (ch) {
                case 'k':
                case 'p':
                case 's':
                case 't':
                        if (ch != 0)
                                usage();
                        mode = ch;
                        break;
                default:
                        usage();
                }
        }
        argc -= optind;
        argv += optind;
        if (argc != 2)
                usage();

        signal(SIGTERM, sighdlr);
        signal(SIGINT, sighdlr);
        signal(SIGHUP, sighdlr);

        fd[0] = open(argv[0], O_RDWR);
        if (fd[0] == -1)
                err(1, "open %s", argv[1]);

        fd[1] = open(argv[1], O_RDWR);
        if (fd[1] == -1)
                err(1, "open %s", argv[2]);

        switch (mode) {
        case 'k':
                do_kqueue(fd);
                break;
        case 'p':
                do_poll(fd);
                break;
        case 's':
                do_select(fd);
                break;
        case 't':
                do_thread(fd);
                break;
        }
        exit(0);
}

Reply via email to