diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2019-08-07 17:17:31 +0000 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2019-08-07 17:44:26 +0000 |
commit | 647a4db4f1a213fd1c9f493f9b4ee59f17c86a87 (patch) | |
tree | e06d9e694750f89891ca2a52377ead5d0d59cc3a | |
download | ulfougretap-647a4db4f1a213fd1c9f493f9b4ee59f17c86a87.tar.gz |
ulfougretap: Userspace L2 GRE tunnel over UDP
Designed for OpenVZ servers running on older kernels without
Foo-over-UDP.
-rw-r--r-- | .gitignore | 20 | ||||
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | README | 32 | ||||
-rw-r--r-- | configure.ac | 30 | ||||
-rw-r--r-- | src/Makefile.am | 5 | ||||
-rw-r--r-- | src/common.h | 72 | ||||
-rw-r--r-- | src/main.c | 150 |
7 files changed, 310 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..396cee2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +Makefile.in +Makefile +autom4te.cache +/aclocal.m4 +/compile +/config.guess +/config.h +/config.h.in +/config.log +/config.status +/config.sub +/configure +/depcomp +/install-sh +/missing +/stamp-h1 + +src/.deps +src/*.o +src/ulfougretap diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..af437a6 --- /dev/null +++ b/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = src @@ -0,0 +1,32 @@ +ulfougretap: Userspace L2 GRE tunnel over UDP +============================================= + +Designed for OpenVZ servers running on older kernels without Foo-over-UDP +support. + +USAGE +----- + +$ autoreconf -i && ./configure && make && make install +$ ulfougretap <dev> <local address> <local port> \ + <peer address> <peer port> + +For example, + + # ulfougretap l2gre0 192.168.1.2 5555 10.0.2.1 5556 + +Does the same thing as: + + # ip fou add port 5555 ipproto 47 \ + local 192.168.1.2 peer 10.0.2.1 peer_port 5556 + # ip link add name l2gre0 type gretap \ + local 192.168.1.2 remote 10.0.2.1 ttl 64 \ + encap fou encap-sport 5555 encap-dport 5556 + + +BUGS +---- + + - IPv6 transport is currently not supported. + - Features of GRE such as Checksum, Key, or Sequence are not supported. + - IP fragment is not supported. Not tested actually. You must properly set MTU. diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..9c7c4de --- /dev/null +++ b/configure.ac @@ -0,0 +1,30 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.69]) +AC_INIT([ulfougretap], [1.0.0], [k@rhe.jp]) +AM_INIT_AUTOMAKE([foreign]) +AC_CONFIG_SRCDIR([src/main.c]) +AC_CONFIG_HEADERS([config.h]) + +# Checks for programs. +AC_PROG_CC + +# Checks for libraries. + +# Checks for header files. +AC_CHECK_HEADERS([arpa/inet.h fcntl.h netinet/in.h stdint.h stdlib.h string.h sys/ioctl.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_INLINE +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T +AC_TYPE_UINT16_T +AC_TYPE_UINT8_T + +# Checks for library functions. +AC_FUNC_ERROR_AT_LINE +AC_CHECK_FUNCS([inet_ntoa memset socket strerror strtol]) + +AC_CONFIG_FILES([Makefile src/Makefile]) +AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..035fc85 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,5 @@ +bin_PROGRAMS = ulfougretap + +ulfougretap_CFLAGS = -std=gnu11 -Wall -Wextra -O3 -ggdb3 + +ulfougretap_SOURCES = main.c common.h diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..ff9d56e --- /dev/null +++ b/src/common.h @@ -0,0 +1,72 @@ +#include "config.h" +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdint.h> +#include <arpa/inet.h> +#include <netinet/in.h> + +#ifndef TUN_DEBUG +# define TUN_DEBUG 0 +#endif + +#define numberof(ary) (sizeof(ary) / sizeof(ary[0])) + +#define print_msg(type) do { \ + va_list args; \ + va_start(args, format); \ + fprintf(stderr, type": "); \ + vfprintf(stderr, format, args); \ + fprintf(stderr, "\n"); \ + va_end(args); \ +} while (0) + +static inline _Noreturn void fatal(const char *format, ...) +{ + print_msg("fatal"); + exit(1); +} + +static inline void error(const char *format, ...) +{ + print_msg("error"); +} + +static inline void info(const char *format, ...) +{ + print_msg("info"); +} + +static inline void debug(const char *format, ...) +{ + print_msg("debug"); +} + +static inline uint16_t parse_u16(const char *str) +{ + long numl = strtol(str, NULL, 10); + if (numl <= 0 || numl > 65535) + fatal("could not parse port number: %s", str); + return (uint16_t)numl; +} + +static inline struct in_addr parse_ipv4(const char *str) +{ + struct in_addr tmp; + int ret = inet_aton(str, &tmp); + if (ret == 0) + fatal("could not parse IPv4 address: %s", str); + return tmp; +} + +static inline void dumpbin(const uint8_t *buf, size_t x) +{ + size_t i; + for (i = 0; i < x; i++) + { + if (i > 0) printf(":"); + printf("%02X", buf[i]); + } + printf("\n"); +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..393f98b --- /dev/null +++ b/src/main.c @@ -0,0 +1,150 @@ +#include "common.h" +#include <unistd.h> +#include <errno.h> +#include <net/if.h> +#include <linux/if_tun.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <poll.h> + +static int tun_fd, utun_fd; +static struct sockaddr_in utun_peer; + +static void setup_tundev(const char *in_ifname) +{ +#define DEVNETTUN "/dev/net/tun" + + int fd = open(DEVNETTUN, O_RDWR); + if (fd < 0) + fatal("setup_tundev: open("DEVNETTUN"): %s", strerror(errno)); + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP|IFF_NO_PI; + strncpy(ifr.ifr_name, in_ifname, IFNAMSIZ - 1); + if (ioctl(fd, TUNSETIFF, &ifr) < 0) + fatal("setup_tundev: ioctl(TUNSETIFF): %s", strerror(errno)); + + debug("setup_tundev: created tuntap interface: %s", in_ifname); + + tun_fd = fd; +} + +static void setup_utun(struct in_addr local_addr, uint16_t local_port, + struct in_addr remote_addr, uint16_t remote_port) +{ + int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + fatal("setup_utun: socket(): %s", strerror(errno)); + + struct sockaddr_in local; + local.sin_family = AF_INET; + local.sin_port = htons(local_port); + local.sin_addr = local_addr; + if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0) + fatal("setup_utun: bind(port=%d): %s", (int)local_port, + strerror(errno)); + + debug("setup_utun: created utun socket: %s:%d", + inet_ntoa(local_addr), (int)local_port); + + struct sockaddr_in remote; + remote.sin_family = AF_INET; + remote.sin_port = htons(remote_port); + remote.sin_addr = remote_addr; + + utun_peer = remote; + utun_fd = fd; +} + +static void read_tundev(void) +{ + const ssize_t gre_header_len = 4; + uint8_t buf[2048]; + // GRE header comes first, inside the UDP packet + buf[0] = 0; + buf[1] = 0; + *(uint16_t *)&buf[2] = htons(0x6558); + + // Ethernet frame follows + ssize_t nread = read(tun_fd, buf + gre_header_len, + sizeof(buf) - gre_header_len); + if (nread < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return; + fatal("tundev: read(): %s", strerror(errno)); + } + + ssize_t nsent = sendto(utun_fd, buf, nread + gre_header_len, 0, + (struct sockaddr *)&utun_peer, + sizeof(utun_peer)); + if (nsent != nread + gre_header_len) + fatal("tundev: sendto(utun): %s", strerror(errno)); + + if (TUN_DEBUG > 0) + fprintf(stderr, ">"); +} + +static void read_utun(void) +{ + const ssize_t gre_header_len = 4; + uint8_t buf[2048]; + struct sockaddr_in remote; + socklen_t remote_len = sizeof(remote); + + ssize_t nread = recvfrom(utun_fd, buf, sizeof(buf), 0, + (struct sockaddr *)&remote, + &remote_len); + if (nread < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return; + fatal("utun: read(): %s", strerror(errno)); + } + if (memcmp(&utun_peer, &remote, sizeof(remote))) { + debug("utun: packet from unexpected peer; expected=%s:%d, " + "actual=%s:%d", inet_ntoa(utun_peer.sin_addr), + (int)utun_peer.sin_port, inet_ntoa(remote.sin_addr), + (int)remote.sin_port); + return; + } + if (nread < gre_header_len || buf[0] != 0 || buf[1] != 0 || + ntohs(*(uint16_t *)&buf[2]) != 0x6558) { + debug("utun: packet in unexpected format"); + return; + } + + ssize_t nwritten = write(tun_fd, buf + gre_header_len, + nread - gre_header_len); + if (nwritten != nread - gre_header_len) + fatal("utun: write(tundev) failed: %s", strerror(errno)); + + if (TUN_DEBUG > 0) + fprintf(stderr, "<"); +} + +int main(int argc, char **argv) +{ + if (argc != 6) + fatal("usage: ulfougretap <dev> <local addr> <local port> " \ + "<peer addr> <peer port>\n"); + + setup_tundev(argv[1]); + setup_utun(parse_ipv4(argv[2]), parse_u16(argv[3]), + parse_ipv4(argv[4]), parse_u16(argv[5])); + + struct pollfd pollfds[2] = { + { .fd = tun_fd, .events = POLLIN, }, + { .fd = utun_fd, .events = POLLIN, }, + }; + + while (1) { + if (poll(pollfds, numberof(pollfds), -1) < 0) + fatal("poll(): %s", strerror(errno)); + + if (pollfds[0].revents) + read_tundev(); + if (pollfds[1].revents) + read_utun(); + } +} |