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); }