/* * gre.c - userspace GRE tunnel * * Copyright (C) 2015 - 2017, Xiaoxiao * Copyright (C) 2019 - 2020, Mikael Magnusson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int tun; static int sock; static struct sockaddr_storage remote; static size_t remote_len; static short type = IFF_TUN; uint8_t buf[4096]; static void gre_cb(void); static void gre_ipv4(const uint8_t *buf, int n); static void gre_ipv6(const uint8_t *buf, int n, const struct sockaddr_in6 *src); static void gre_any(const uint8_t *buf, int n); static int tun_cb(void); static int tun_new(short type, const char *dev); static int setnonblock(int fd); static int runas(const char *user); static int daemonize(void); static int inet_addr_storage(const char *cp, struct sockaddr_storage *sp, size_t *sp_len); int main(int argc, char **argv) { fd_set readset; while (1) { int option_index = 0; static struct option long_options[] = { {"tun", no_argument, 0, 'n' }, {"tap", no_argument, 0, 'p' }, {0, 0, 0, 0 } }; char c = getopt_long(argc, argv, "np", long_options, &option_index); if (c == -1) break; switch (c) { case 'n': printf("tun\n"); type = IFF_TUN; break; case 'p': printf("tap\n"); type = IFF_TAP; break; case '?': break; default: printf("?? getopt returned character code 0%o ??\n", c); } } if ( (argc - optind) != 3) { printf("usage: %s [-n|--tun|-p|--tap] remote local\n", argv[0]); return EXIT_FAILURE; } const char *dev = argv[optind++]; const char *remote_opt = argv[optind++]; const char *local_opt = argv[optind++]; tun = tun_new(type, dev); if (tun < 0) { printf("failed to init %s device\n", type == IFF_TAP ? "tap" : "tun"); return EXIT_FAILURE; } struct sockaddr_storage local; size_t local_len = 0; if (inet_addr_storage(local_opt, &local, &local_len)) { fprintf(stderr, "bad local address\n"); return EXIT_FAILURE; } sock = socket(local.ss_family, SOCK_RAW, IPPROTO_GRE); if (sock < 0) { perror("socket"); return EXIT_FAILURE; } if (bind(sock, (struct sockaddr *)&local, local_len) != 0) { perror("bind"); return EXIT_FAILURE; } if (inet_addr_storage(remote_opt, &remote, &remote_len)) { fprintf(stderr, "bad remote address\n"); return EXIT_FAILURE; } setnonblock(sock); setnonblock(tun); runas("nobody"); daemonize(); int maxfd = (tun > sock ? tun : sock) + 1; while (1) { FD_ZERO(&readset); FD_SET(tun, &readset); FD_SET(sock, &readset); int r = select(maxfd, &readset, NULL, NULL, NULL); if (r < 0) { if (errno == EINTR) { continue; } else { perror("select"); break; } } if (FD_ISSET(sock, &readset)) { gre_cb(); } if (FD_ISSET(tun, &readset)) { if (tun_cb() < 0) return 0; } } return 0; } static void gre_cb(void) { int n; struct sockaddr_storage src; socklen_t src_len = sizeof(src); memset(&src, 0, src_len); n = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&src, &src_len); if (n < 0) { perror("recv"); return; } switch (remote.ss_family) { case AF_INET: gre_ipv4(buf, n); break; case AF_INET6: gre_ipv6(buf, n, (const struct sockaddr_in6*)&src); break; } } static void gre_ipv4(const uint8_t *buf, int n) { int ihl; // IP header length ihl = 4 * (buf[0] & 0x0f); if (ihl > 60 || ihl < 20) { printf("IPv4 header too long\n"); return; } // check source IPv4 address const struct sockaddr_in *remote_in = (const struct sockaddr_in *)&remote; if (*(uint32_t *)(buf + 12) != remote_in->sin_addr.s_addr) { return; } gre_any(buf + ihl, n - ihl); } static void gre_ipv6(const uint8_t *buf, int n, const struct sockaddr_in6 *src) { if (n < 40) { return; } // check source IPv6 address const struct sockaddr_in6 *remote_in6 = (const struct sockaddr_in6 *)&remote; if (memcmp(src->sin6_addr.s6_addr, remote_in6->sin6_addr.s6_addr, 16) != 0) { return; } gre_any(buf, n); } static void gre_any(const uint8_t *buf, int n) { // parse GRE header if (*(uint16_t *)(buf) != 0) { return; } uint16_t protocol = ntohs(*(uint16_t *)(buf + 2)); if (protocol != ETHERTYPE_IP && protocol != ETHERTYPE_IPV6) { return; } write(tun, buf, n); } static int tun_cb(void) { int n; n = read(tun, buf, sizeof(buf)); if (n < 0) { int err = errno; perror("read"); if (err == EBADFD) return -1; return 0; } uint16_t proto = ntohs(*(uint16_t *)(buf + 2)); uint8_t *frame; if (type == IFF_TAP) { size_t ethsize = 6 + 6 + 2; frame = buf + ethsize; n = n - ethsize; frame[2] = buf[2]; frame[3] = buf[3]; } else { frame = buf; } frame[0] = 0; frame[1] = 0; switch (proto) { case ETHERTYPE_IP: case ETHERTYPE_IPV6: break; case ETHERTYPE_ARP: if (type == IFF_TAP) break; return 0; default: return 0; } sendto(sock, frame, n, 0, (struct sockaddr *)&remote, remote_len); return 0; } static int tun_new(short type, const char *dev) { struct ifreq ifr; int fd, err; fd = open("/dev/net/tun", O_RDWR); if (fd < 0) { return -1; } bzero(&ifr, sizeof(struct ifreq)); ifr.ifr_flags = type; if (*dev != '\0') { strncpy(ifr.ifr_name, dev, IFNAMSIZ); } err = ioctl(fd, TUNSETIFF, (void *)&ifr); if (err < 0) { return err; } return fd; } static int setnonblock(int fd) { int flags; flags = fcntl(fd, F_GETFL, 0); if (flags == -1) { return -1; } if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK)) { return -1; } return 0; } static int runas(const char *user) { struct passwd *pw_ent = getpwnam(user); if (pw_ent != NULL) { if (setegid(pw_ent->pw_gid) != 0) { return -1; } if (seteuid(pw_ent->pw_uid) != 0) { return -1; } } return 0; } static int daemonize(void) { pid_t pid; pid = fork(); if (pid < 0) { perror("fork"); return -1; } if (pid > 0) { exit(0); } umask(0); if (setsid() < 0) { perror("setsid"); return -1; } return 0; } static int inet_addr_storage(const char *cp, struct sockaddr_storage *sp, size_t *sp_len) { struct addrinfo hints; struct addrinfo *result = NULL; int res; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_NUMERICHOST | AI_ADDRCONFIG; res = getaddrinfo(cp, NULL, &hints, &result); if (res != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(res)); return -1; } memcpy(sp, result->ai_addr, result->ai_addrlen); *sp_len = result->ai_addrlen; freeaddrinfo(result); result = NULL; if (sp->ss_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)sp; sin->sin_port = htons(IPPROTO_GRE); } else if (sp->ss_family == AF_INET6) { struct sockaddr_in6 *sin = (struct sockaddr_in6 *)sp; sin->sin6_port = htons(IPPROTO_GRE); } return 0; }