INSTALL | 9 +++++++++ README | 8 ++++++++ USAGE | 11 +++++++++++ USAGE.FreeBSD | 14 ++++++++++++++ USAGE.GNU+Linux | 7 +++++++ build | 11 +++++++++++ tapser.c | 489 +++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000000000000000000000000000000000000..98f7b8ba45886fdca12e495e879e27ff7a66b813994c7adda8307627f6c799ad --- /dev/null +++ b/INSTALL @@ -0,0 +1,9 @@ + $ curl -L https://api.github.com/repos/Cyan4973/xxHash/tarball/v0.8.3 | tar xf - + $ cp Cyan4973-xxHash-e626a72/xxhash.[ch] . + $ ./build -O3 + +Look in xxhash.h for its related options. + +To enable Zstandard compression: + + $ ./build -DTAPSER_ENABLE_ZSTD -lzstd diff --git a/README b/README new file mode 100644 index 0000000000000000000000000000000000000000..a0b79eaccb43cea96acd68e56233981455af80b39509faf6be94cb937cb32885 --- /dev/null +++ b/README @@ -0,0 +1,8 @@ +tapser -- TAP/TUN over serial + +tapser connects TAP/TUN interface via serial connection with the remote +instance using SLIP protocol (RFC 1055, double ENDs). It configures TTY +device to raw 8N1 mode (configurable speed, 115200 by default). + +Optional XXH3-64 checksum can be used, which is recommended for IPv6. +Optional Zstandard compression can be used too. diff --git a/USAGE b/USAGE new file mode 100644 index 0000000000000000000000000000000000000000..8a9a7b5a58d41ea145d3befa8b7b02e39b3c4867306380b2144ff932ab4faf90 --- /dev/null +++ b/USAGE @@ -0,0 +1,11 @@ +First argument is a path to TTY device. +Second one is the name of the network interface. + +-u CMD option allows running the sh -c CMD after the interface is +created. TAPSER_IF environment variable holds its name. + +-t option switches from TAP to TUN mode of operation. + +-n option disables checksum usage, using plain SLIP. + +-z enabled Zstandard compression. That disables checksumming. diff --git a/USAGE.FreeBSD b/USAGE.FreeBSD new file mode 100644 index 0000000000000000000000000000000000000000..9e34147c34dd639f5715476222ce632fbbf27d3c53c10f46886aec9b1576e5fb --- /dev/null +++ b/USAGE.FreeBSD @@ -0,0 +1,14 @@ +Network interface can be created beforehand, specifying full path to it: + + # ifconfig tap create name tap123 + # tapser /dev/nmdm123B /dev/tap123 + +Interface can be automatically created, specifying /dev/tap: + + # tapser -u ' + ifconfig $TAPSER_IF inet6 fe80::1/64 -ifdisabled auto_linklocal up + ' /dev/nmdm123B /dev/tap + +For TUN interfaces use /dev/tun: + + # tapser -t -u 'echo $TAPSER_IF' /dev/nmdm123B /dev/tun diff --git a/USAGE.GNU+Linux b/USAGE.GNU+Linux new file mode 100644 index 0000000000000000000000000000000000000000..9f0d18f9f00665772a4017dc2c1c795249fc13c7cbd2ea3ff7133a690e8f957e --- /dev/null +++ b/USAGE.GNU+Linux @@ -0,0 +1,7 @@ +Network interface is created with the specified name. +Reusing is currently not done. + + # tapser -u ' + ip link set up dev $TAPSER_IF + ip addr add fe80::1/64 dev $TAPSER_IF + ' /dev/ttyS1 tap123 diff --git a/build b/build new file mode 100755 index 0000000000000000000000000000000000000000..3a215ad4d703d644545379103afc92c9860f402fc16dd56e99a4ef27d715100e --- /dev/null +++ b/build @@ -0,0 +1,11 @@ +#!/bin/sh -e + +exec ${CC:-cc} -o tapser \ + -std=c99 -pipe -fno-omit-frame-pointer \ + -fstrict-aliasing -fwrapv \ + -ffunction-sections -fdata-sections \ + $CFLAGS \ + -D_DEFAULT_SOURCE \ + -DXXH_NO_STREAM \ + $@ tapser.c xxhash.c \ + $LDFLAGS $LDLIBS -Wl,--gc-sections diff --git a/tapser.c b/tapser.c new file mode 100644 index 0000000000000000000000000000000000000000..352dc720dd6b7a65c445ad38fb41fcbd28ddd21f254da91715de06922d7a876e --- /dev/null +++ b/tapser.c @@ -0,0 +1,489 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#include +#include +#else +#include +#include +#include +#endif // __linux__ + +#include "xxhash.h" + +#ifdef TAPSER_ENABLE_ZSTD +#include +#endif // TAPSER_ENABLE_ZSTD + +enum { + End = 192, + Esc = 219, + EscEnd = 220, + EscEsc = 221, + + MaxPktLen = 16 * 1024, + TUNHdrLen = 4, + +#ifdef __linux__ + HdrIPv4_0 = (ETHERTYPE_IP & 0xFF00 >> 8), + HdrIPv4_1 = (ETHERTYPE_IP & 0x00FF), + HdrIPv6_0 = (ETHERTYPE_IPV6 & 0xFF00 >> 8), + HdrIPv6_1 = (ETHERTYPE_IPV6 & 0x00FF), +#else + HdrIPv4_0 = 0, + HdrIPv4_1 = AF_INET, + HdrIPv6_0 = 0, + HdrIPv6_1 = AF_INET6, +#endif // __linux__ +}; + +#ifdef __linux__ +static int +netOpen(char **name, const char *pth, const bool isTUN) +{ + int fd = open("/dev/net/tun", O_RDWR); + if (fd == -1) { + perror("open (net)"); + return -1; + } + struct ifreq ifr = {0}; + ifr.ifr_flags = isTUN ? IFF_TUN : (IFF_TAP | IFF_NO_PI); + strncpy(ifr.ifr_name, pth, IFNAMSIZ); + if ((ioctl(fd, TUNSETIFF, (void *)&ifr)) == -1) { + perror("ioctl(TUNSETIFF)"); + close(fd); + return -1; + } + *name = strdup(pth); + return fd; +} +#else // __linux__ +static int +netOpen(char **name, const char *pth, const bool isTUN) +{ + int fd = open(pth, O_RDWR); + if (fd == -1) { + perror("open(net)"); + return fd; + } + if ((strcmp(pth, "/dev/tun") == 0) || (strcmp(pth, "/dev/tap") == 0)) { + struct ifreq ifr = {0}; + if (ioctl(fd, isTUN ? TUNGIFNAME : TAPGIFNAME, &ifr) == -1) { + perror("ioctl(GIFNAME)"); + goto Err; + } + *name = strdup(ifr.ifr_name); + } else { + *name = strdup(pth); + } + if (isTUN) { + int flag = 1; + if (ioctl(fd, TUNSIFHEAD, &flag) == -1) { + perror("ioctl(TUNSIFHEAD)"); + goto Err; + } + } + return fd; +Err: + close(fd); + return -1; +} +#endif // __linux__ + +static int +ttyOpen(const char *pth, const speed_t speed) +{ + int fd = open(pth, O_RDWR | O_NOCTTY | O_SYNC); + if (fd == -1) { + perror("open(tty)"); + return -1; + } + struct termios tty = {0}; + if (tcgetattr(fd, &tty) != 0) { + perror("tcgetattr()"); + close(fd); + return -1; + } + cfmakeraw(&tty); + cfsetspeed(&tty, speed); + tty.c_cflag &= ~(CSTOPB | PARENB); + tty.c_cflag |= CLOCAL | CREAD; + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + perror("tcsetattr()"); + close(fd); + return -1; + } + return fd; +} + +static void +usage() +{ + // clang-format off + fputs("Usage: tapser [-n] [-z] [-s speed] [-u cmd] [-d cmd] /dev/ttyX tapX\n", stderr); + fputs("tapX is desirable name for the newly created TUN/TAP interface on GNU/Linux\n", stderr); + fputs("tapX can be full path to already existing /dev/{tap,tun}X device on *BSD\n", stderr); + fputs("tapX can be /dev/{tap,tun} to create new one on *BSD\n", stderr); + fputs("-n disables checksum usage\n", stderr); + fputs("-s sets the speed of TTY, by default to 115200\n", stderr); + fputs("-u specifies shell command to be run after interface is ready\n", stderr); + fputs("-d specifies shell command to be run when we exit\n", stderr); + fputs("-z enable compression\n", stderr); + // clang-format on +} + +int +main(int argc, char *argv[]) +{ + bool useCsum = true; + bool isTUN = false; + bool compress = false; +#ifdef TAPSER_ENABLE_ZSTD + struct ZSTD_CCtx_s *zC = ZSTD_createCCtx(); + struct ZSTD_DCtx_s *zD = ZSTD_createDCtx(); + int zLevel = ZSTD_defaultCLevel(); +#endif // TAPSER_ENABLE_ZSTD + speed_t speed = 115200; + char *up = NULL; + char *down = NULL; + char *ifname = NULL; + int exitCode = EXIT_FAILURE; + extern char *optarg; + extern int optind; + for (int c = 0;;) { + c = getopt(argc, argv, "ns:u:d:tz"); + if (c == -1) { + break; + } + switch (c) { + case 'n': + useCsum = false; + break; + case 's': + errno = 0; + speed = (speed_t)strtol(optarg, NULL, 10); + if (errno != 0) { + perror("can not parse -s"); + goto Free; + } + break; + case 'u': + if (up != NULL) { + free(up); + } + up = strdup(optarg); + break; + case 'd': + if (down != NULL) { + free(down); + } + down = strdup(optarg); + break; + case 't': + isTUN = true; + break; + case 'z': +#ifdef TAPSER_ENABLE_ZSTD + compress = true; +#else // TAPSER_ENABLE_ZSTD + fprintf(stderr, "zstd was not enabled during build\n"); + goto Free; +#endif // TAPSER_ENABLE_ZSTD + break; + case '?': + default: + usage(); + goto Free; + } + } + if ((argc - optind) != 2) { + usage(); + goto Free; + } + if (compress) { + useCsum = false; + } + + struct pollfd fds[2]; + memset(&fds, 0, sizeof fds); + struct pollfd *net = &(fds[0]); + struct pollfd *tty = &(fds[1]); + + tty->fd = ttyOpen(argv[optind], speed); + if (tty->fd == -1) { + goto Free; + } + tty->events = POLLIN; + tty->revents = 0; + net->fd = netOpen(&ifname, argv[optind + 1], isTUN); + if (net->fd == -1) { + goto Free; + } + net->events = POLLIN; + net->revents = 0; + printf("%s <-> %s\n", argv[optind], ifname); + setenv("TAPSER_IF", ifname, 1); + if (up != NULL) { + errno = 0; + if (system(up) != 0) { + perror("system(up)"); + goto Free; + } + } + + unsigned char buf[2 * MaxPktLen] = {0}; + unsigned char tx[2 * MaxPktLen] = {0}; + unsigned char rx[2 * MaxPktLen] = {0}; + size_t rxLen = TUNHdrLen; + bool hasEnd = false; + bool hasEsc = false; + for (;;) { + if (poll(fds, 2, -1) == -1) { + if (errno == EINTR) { + continue; + } + perror("poll()"); + continue; + } + if ((net->revents & (POLLERR | POLLNVAL | POLLHUP)) > 0) { + fprintf(stderr, "net err: %d\n", net->revents); + goto Down; + } + if ((tty->revents & (POLLERR | POLLNVAL | POLLHUP)) > 0) { + fprintf(stderr, "tty err: %d\n", tty->revents); + goto Down; + } + if (net->revents != 0) { + ssize_t r = read(net->fd, buf, MaxPktLen); + if (r == -1) { + perror("read(net)"); + goto Down; + } + size_t srcLen = (size_t)r; + size_t pktLen = srcLen; + if (srcLen == (sizeof buf) - 8) { + fprintf(stderr, "> %zu\ttoo big\n", srcLen); + continue; + } + if (srcLen < TUNHdrLen + 1) { + fprintf(stderr, "> %zu\ttoo small\n", srcLen); + continue; + } + unsigned char *src = buf; + unsigned char *dst = tx; + if (isTUN) { + src += TUNHdrLen; + srcLen -= TUNHdrLen; + pktLen = srcLen; + const unsigned char c = (src[0]) >> 4; + if ((c != 4) && (c != 6)) { + fprintf(stderr, "> %zu\t!IP: %d\n", pktLen, src[0]); + continue; + } + } + if (useCsum) { + XXH64_canonicalFromHash( + (XXH64_canonical_t *)(src + srcLen), XXH3_64bits(src, srcLen)); + srcLen += 8; + } +#ifdef TAPSER_ENABLE_ZSTD + if (compress) { + const size_t s = + ZSTD_compressCCtx(zC, dst, 2 * MaxPktLen, src, srcLen, zLevel); + if (ZSTD_isError(s)) { + fprintf( + stderr, + "> %zu\tcompress error: %s\n", + pktLen, + ZSTD_getErrorName(s)); + continue; + } + srcLen = s; + src = dst; + dst = buf; + } +#endif // TAPSER_ENABLE_ZSTD + dst[0] = End; + size_t txLen = 1; + for (size_t i = 0; i < srcLen; i++) { + switch (src[i]) { + case End: + dst[txLen] = Esc; + dst[txLen + 1] = EscEnd; + txLen += 2; + break; + case Esc: + dst[txLen] = Esc; + dst[txLen + 1] = EscEsc; + txLen += 2; + break; + default: + dst[txLen] = src[i]; + txLen++; + } + } + dst[txLen] = End; + txLen++; + printf("> %zu\t%zu\n", pktLen, txLen); + for (size_t i = 0; i < txLen;) { + const ssize_t w = write(tty->fd, dst + i, txLen - i); + if (w == -1) { + perror("write(tty)"); + goto Down; + } + i += (size_t)w; + } + } + if (tty->revents != 0) { + ssize_t r = read(tty->fd, buf, sizeof buf); + if (r == -1) { + perror("read(tty)"); + goto Down; + } + for (size_t i = 0; i < (size_t)r; i++) { + if (rxLen == MaxPktLen) { + fprintf(stderr, "tty full: %zu\n", rxLen); + rxLen = TUNHdrLen; + hasEnd = false; + } + if (hasEsc) { + switch (buf[i]) { + case EscEsc: + rx[rxLen] = Esc; + rxLen++; + break; + case EscEnd: + rx[rxLen] = End; + rxLen++; + break; + default: + fprintf(stderr, "< %zu\tbad esc: %zu\n", (size_t)r, i); + } + hasEsc = false; + continue; + } + if (buf[i] == Esc) { + hasEsc = true; + continue; + } + if (buf[i] != End) { + if (hasEnd) { + rx[rxLen] = buf[i]; + rxLen++; + } + continue; + } + if (!hasEnd) { + hasEnd = true; + continue; + } + if (rxLen >= MaxPktLen) { + fprintf(stderr, "< %zu\ttoo big\n", rxLen); + goto PktDone; + } + const size_t rxLenOrig = rxLen; + unsigned char *src = rx; + if (useCsum) { + if (rxLen < TUNHdrLen + 8) { + fprintf(stderr, "< %zu\ttoo short\n", rxLenOrig); + goto PktDone; + } + if (XXH64_hashFromCanonical( + (const XXH64_canonical_t *)(rx + rxLen - 8)) != + XXH3_64bits(rx + TUNHdrLen, rxLen - TUNHdrLen - 8)) { + fprintf(stderr, "< %zu\tbad csum\n", rxLen); + goto PktDone; + } + rxLen -= 8; + } +#ifdef TAPSER_ENABLE_ZSTD + if (compress) { + const size_t s = ZSTD_decompressDCtx( + zD, + tx + TUNHdrLen, + (sizeof tx) - TUNHdrLen, + rx + TUNHdrLen, + rxLen - TUNHdrLen); + if (ZSTD_isError(s)) { + fprintf( + stderr, + "< %zu\tdecompress error: %s\n", + rxLenOrig, + ZSTD_getErrorName(s)); + goto PktDone; + } + src = tx; + rxLen = TUNHdrLen + s; + } +#endif // TAPSER_ENABLE_ZSTD + if (isTUN) { + src[0] = 0; + src[1] = 0; + switch (src[TUNHdrLen] >> 4) { + case 4: + src[2] = HdrIPv4_0; + src[3] = HdrIPv4_1; + break; + case 6: + src[2] = HdrIPv6_0; + src[3] = HdrIPv6_1; + break; + default: + fprintf(stderr, "< %zu\t!IP: %d\n", rxLenOrig, src[TUNHdrLen]); + goto PktDone; + } + } + printf("< %zu\t%zu\n", rxLenOrig, rxLen - (isTUN ? TUNHdrLen : 0)); + if (write( + net->fd, + src + (isTUN ? 0 : TUNHdrLen), + rxLen - (isTUN ? 0 : TUNHdrLen)) == -1) { + perror("write(net)"); + } + PktDone: + rxLen = TUNHdrLen; + hasEnd = false; + continue; + } + } + } + +Down: + if (down != NULL) { + errno = 0; + if (system(down) != 0) { + perror("system(down)"); + } + } + exitCode = EXIT_SUCCESS; + +Free: + if (up != NULL) { + free(up); + } + if (down != NULL) { + free(down); + } + if (ifname != NULL) { + free(ifname); + } +#ifdef TAPSER_ENABLE_ZSTD + ZSTD_freeCCtx(zC); + ZSTD_freeDCtx(zD); +#endif + return exitCode; +}