Created
July 12, 2018 19:42
-
-
Save timkuijsten/954f3f1e8e84980a930a1a6a591b96ee to your computer and use it in GitHub Desktop.
unencrypted udp based vpn tunnel
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* A simple unencrypted UDP tunnel between two tunnel devices. */ | |
#include <sys/ioctl.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <arpa/inet.h> | |
#include <net/route.h> | |
#include <net/if.h> | |
#include <net/if_tun.h> | |
#include <netinet6/in6_var.h> | |
#include <err.h> | |
#include <errno.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <fcntl.h> | |
#include <ifaddrs.h> | |
#include <limits.h> | |
#include <netdb.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#define MAXTUN 1024 /* the maximum number of supported tunnel devices */ | |
#define MAXPORT 65535 | |
#define TUNHDRSZ 4 | |
#define DEFAULTPORT "2389" | |
#define EMPTYDIR "/var/empty" | |
static int verbose; | |
/* | |
* Write numeric hostname and/or service name into host and serv. Each must have | |
* size of at least NI_MAXHOST and NI_MAXSERV, respectively. | |
* | |
* Return 0 on success or an integer for gai_strerror on error. | |
*/ | |
int | |
addrtostr(const struct sockaddr *sa, char *host, char *serv) | |
{ | |
return getnameinfo(sa, sa->sa_len, host, host ? NI_MAXHOST : 0, | |
serv, serv ? NI_MAXSERV : 0, NI_NUMERICHOST | NI_NUMERICSERV); | |
} | |
void | |
printaddr(const char *msg, const struct sockaddr *sa) | |
{ | |
char aihost[NI_MAXHOST], aiserv[NI_MAXSERV]; | |
int e; | |
e = addrtostr(sa, aihost, aiserv); | |
if (e) | |
warnx("%s %s", msg ? msg : "", gai_strerror(e)); | |
else | |
warnx("%s %s %s", msg ? msg : "", aihost, aiserv); | |
} | |
/* XXX somehow doesn't work. */ | |
int | |
assignaddr6(int s, const char *ifname, const struct sockaddr_in6 *addr, const | |
struct sockaddr_in6 *mask) | |
{ | |
struct in6_aliasreq addreq; | |
if (ifname == NULL || addr == NULL || mask == NULL) { | |
errno = EINVAL; | |
return -1; | |
} | |
memset(&addreq, 0, sizeof(addreq)); | |
if (strlcpy(addreq.ifra_name, ifname, sizeof(addreq.ifra_name)) >= | |
sizeof(addreq.ifra_name)) { | |
errno = EINVAL; | |
return -1; | |
} | |
memcpy(&addreq.ifra_addr, addr, sizeof(addreq.ifra_addr)); | |
memcpy(&addreq.ifra_prefixmask, mask, sizeof(addreq.ifra_prefixmask)); | |
if (ioctl(s, SIOCAIFADDR_IN6, &addreq) == -1) | |
return -1; | |
return 0; | |
} | |
/* | |
* Assign an address to an interface. | |
* | |
* Return 0 on success and -1 on failure with errno set. | |
*/ | |
int | |
assignaddr4(int s, const char *ifname, const struct sockaddr_in *addr, const | |
struct sockaddr_in *mask) | |
{ | |
struct ifaliasreq addreq; | |
if (ifname == NULL || addr == NULL || mask == NULL) { | |
errno = EINVAL; | |
return -1; | |
} | |
memset(&addreq, 0, sizeof(addreq)); | |
if (strlcpy(addreq.ifra_name, ifname, sizeof(addreq.ifra_name)) >= | |
sizeof(addreq.ifra_name)) { | |
errno = EINVAL; | |
return -1; | |
} | |
memcpy(&addreq.ifra_addr, addr, sizeof(addreq.ifra_addr)); | |
memcpy(&addreq.ifra_mask, mask, sizeof(addreq.ifra_mask)); | |
if (ioctl(s, SIOCAIFADDR, &addreq) == -1) | |
return -1; | |
return 0; | |
} | |
/* | |
* Assign the given address to an interface. | |
* | |
* Return 0 on success or -1 on failure with errno set. | |
*/ | |
int | |
assignaddr(const char *ifname, const struct sockaddr *addr, const struct | |
sockaddr *mask) | |
{ | |
int s, r; | |
s = -1; | |
r = -1; | |
if (ifname == NULL || addr == NULL || mask == NULL) { | |
errno = EINVAL; | |
return -1; | |
} | |
if (addr->sa_family != AF_INET && addr->sa_family != AF_INET6) { | |
errno = EINVAL; | |
return -1; | |
} | |
if (verbose > 0) { | |
printaddr("addr", addr); | |
printaddr("mask", mask); | |
} | |
if ((s = socket(addr->sa_family, SOCK_DGRAM, 0)) == -1) | |
errx(1, "socket"); | |
/* Assign address and netmask. */ | |
if (addr->sa_family == AF_INET) | |
r = assignaddr4(s, ifname, (const struct sockaddr_in *)addr, | |
(const struct sockaddr_in *)mask); | |
else /* AF_INET6 */ | |
r = assignaddr6(s, ifname, (const struct sockaddr_in6 *)addr, | |
(const struct sockaddr_in6 *)mask); | |
if (s >= 0) | |
close(s); | |
return r; | |
} | |
/* | |
* Check if a tunnel device has a certain address. | |
* | |
* Return 1 of true, 0 if not. | |
*/ | |
int | |
tunhasaddr(const char *ifname, const struct sockaddr *sa) | |
{ | |
struct ifaddrs *ifa, *ifa0; | |
if (ifname == NULL || sa == NULL) | |
errx(1, "%s", __func__); | |
if (getifaddrs(&ifa0) == -1) | |
err(1, "getifaddrs"); | |
for (ifa = ifa0; ifa; ifa = ifa->ifa_next) { | |
if (strcmp(ifa->ifa_name, ifname) != 0) | |
continue; | |
if (ifa->ifa_addr == NULL) | |
continue; | |
if (ifa->ifa_addr->sa_len != sa->sa_len) | |
continue; | |
if (memcmp(ifa->ifa_addr, sa, sa->sa_len) != 0) | |
continue; | |
/* Match. */ | |
freeifaddrs(ifa0); | |
return 1; | |
} | |
freeifaddrs(ifa0); | |
return 0; | |
} | |
/* | |
* Find a tunnel device with the given ip and prefixlen. | |
* | |
* Return the number of the tunnel interface with the ip and prefix, -1 if not | |
* found. If maxtun is not null it will be set to the maximum encountered | |
* existing tunnel device or -1 if the host has no tunnel devices. | |
*/ | |
int | |
findtun(const struct sockaddr *sa, int *maxtun) | |
{ | |
struct ifaddrs *ifa, *ifa0; | |
struct sockaddr_in *sinp, *sinp2; | |
struct sockaddr_in6 *sin6p, *sin6p2; | |
char buf[1024]; | |
int i, match, maxt, tunidx; | |
if (getifaddrs(&ifa0) == -1) | |
err(1, "getifaddrs"); | |
tunidx = -1; | |
maxt = -1; | |
/* | |
* Find a tunnel with the given address and find the tunnel device with | |
* the highest number, no matter it's address. | |
*/ | |
if (sa->sa_family == AF_INET) { | |
sinp = (struct sockaddr_in *)sa; | |
if (inet_ntop(sa->sa_family, &sinp->sin_addr, buf, | |
sizeof(buf)) == NULL) | |
err(1, "inet_ntop"); | |
} else if (sa->sa_family == AF_INET6) { | |
sin6p = (struct sockaddr_in6 *)sa; | |
if (inet_ntop(sa->sa_family, &sin6p->sin6_addr, buf, | |
sizeof(buf)) == NULL) | |
err(1, "inet_ntop"); | |
} else | |
errx(1, "unsupported protocol"); | |
if (verbose > 1) | |
warnx("search interface %s...", buf); | |
/* | |
* Loop over all tunnel devices and check if the ip exists. | |
*/ | |
for (ifa = ifa0; ifa; ifa = ifa->ifa_next) { | |
match = 0; | |
/* | |
* Skip non-tunnel devices, devices without an address | |
* and devices with an address from another family. | |
*/ | |
if (strncmp(ifa->ifa_name, "tun", 3) != 0) | |
continue; | |
/* This is a tunnel interface, save it's number. */ | |
if (sscanf(ifa->ifa_name, "tun%d", &i) != 1) | |
errx(1, "could not parse device name %s", | |
ifa->ifa_name); | |
if (i < 0) | |
errx(1, "unsupported tunnel number %d", i); | |
if (i > MAXTUN) | |
errx(1, "unsupported tunnel number %d, only %d" | |
" tunnel devices are supported", i, MAXTUN); | |
if (i > maxt) | |
maxt = i; | |
if (ifa->ifa_addr == NULL) | |
continue; | |
if (ifa->ifa_addr->sa_family != sa->sa_family) | |
continue; | |
if (sa->sa_family == AF_INET) { | |
sinp = (struct sockaddr_in *)ifa->ifa_addr; | |
sinp2 = (struct sockaddr_in *)sa; | |
if (inet_ntop(sa->sa_family, &sinp->sin_addr, buf, | |
sizeof(buf)) == NULL) | |
err(1, "inet_ntop"); | |
if (memcmp(&sinp->sin_addr, &sinp2->sin_addr, | |
sizeof(sinp->sin_addr)) == 0) | |
match = 1; | |
} else if (sa->sa_family == AF_INET6) { | |
sin6p = (struct sockaddr_in6 *)ifa->ifa_addr; | |
sin6p2 = (struct sockaddr_in6 *)sa; | |
if (inet_ntop(sa->sa_family, &sin6p->sin6_addr, | |
buf, sizeof(buf)) == NULL) | |
err(1, "inet_ntop"); | |
if (memcmp(&sin6p->sin6_addr, | |
&sin6p2->sin6_addr, | |
sizeof(sin6p->sin6_addr)) == 0) | |
match = 1; | |
} | |
if (!match) | |
continue; | |
/* This interface has the address. */ | |
tunidx = i; | |
warnx("match %s %s", ifa->ifa_name, buf); | |
} | |
if (maxtun) | |
*maxtun = maxt; | |
freeifaddrs(ifa0); | |
return tunidx; | |
} | |
/* Open a tunnel device or return -1 with errno set on error. */ | |
int | |
opentunnel(const char *ifname) | |
{ | |
int fd, flags; | |
char *cp; | |
if (asprintf(&cp, "/dev/%s", ifname) <= 0) | |
errx(1, "asprintf"); | |
fd = open(cp, O_RDWR); | |
free(cp); | |
cp = NULL; | |
if (fd == -1) | |
return -1; | |
flags = IFF_UP | IFF_BROADCAST; | |
if (ioctl(fd, TUNSIFMODE, &flags) == -1) { | |
close(fd); | |
return -1; | |
} | |
return fd; | |
} | |
/* | |
* Convert a string representation of an ip address and/or port to a sockaddr | |
* structure. | |
* | |
* Return 0 on success, or a number for gai_strerror on error. Only on success | |
* res will be updated. | |
*/ | |
int | |
strtoaddr(const char *ip, const char *port, struct sockaddr **res) | |
{ | |
struct addrinfo hints, *ai; | |
int e; | |
memset(&hints, 0, sizeof(hints)); | |
hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; | |
hints.ai_family = AF_UNSPEC; | |
hints.ai_socktype = SOCK_DGRAM; | |
hints.ai_protocol = IPPROTO_UDP; | |
e = getaddrinfo(ip, port, &hints, &ai); | |
if (e) | |
return e; | |
if ((*res = malloc(ai->ai_addr->sa_len)) == NULL) | |
err(1, "malloc"); | |
memcpy(*res, ai->ai_addr, ai->ai_addr->sa_len); | |
freeaddrinfo(ai); | |
return 0; | |
} | |
/* | |
* Forward data from one descriptor to another. | |
*/ | |
void | |
fwddtod(int src, int dst) | |
{ | |
char buf[131072]; /* 2^17 */ | |
char *payload; | |
int i, j; | |
warnx("[%d] %s", getpid(), __func__); | |
while ((i = read(src, buf, sizeof(buf))) > 0) { | |
warnx("[%d] read %d bytes", getpid(), i); | |
payload = buf; | |
while (i && ((j = write(dst, payload, i)) > 0)) { | |
i -= j; | |
payload += j; | |
warnx("[%d] written %d bytes, next %d", getpid(), j, i); | |
} | |
warnx("[%d] done, written %d bytes", getpid(), j); | |
if (j == -1) | |
err(1, "[%d] write", getpid()); | |
} | |
warnx("[%d] done, read %d bytes", getpid(), i); | |
if (i == -1) | |
err(1, "[%d] read", getpid()); | |
} | |
/* | |
* Start a server on the given address. | |
* | |
* Return a listening socket or exit on error. | |
*/ | |
int | |
bindserver(const struct sockaddr *sa, int sock_type) | |
{ | |
int s, i; | |
if ((s = socket(sa->sa_family, sock_type, 0)) == -1) | |
errx(1, "socket"); | |
if (sock_type == SOCK_STREAM) { | |
i = 1; | |
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) == | |
-1) | |
err(1, "setsockopt"); | |
} | |
if (bind(s, sa, sa->sa_len) == -1) | |
err(1, "bind"); | |
return s; | |
} | |
/* | |
* Start listen for connections. | |
* | |
* Return the listening socket or exit on error. | |
*/ | |
int | |
startlistening(s) | |
{ | |
if (listen(s, 5) == -1) | |
err(1, "listen"); | |
return s; | |
} | |
void | |
prusage(FILE *out) | |
{ | |
fprintf(out, "usage: %s [-Vdh] [-D tundev] [-m tunmask] [-b localip] [-p localport] tunip" | |
" remoteip [remoteport]\n", getprogname()); | |
} | |
int | |
main(int argc, char *argv[]) | |
{ | |
struct sockaddr *lsa, *rsa, *tunmasksa, *tunsa; | |
struct sockaddr_storage ss; | |
char aihost[NI_MAXHOST], aiserv[NI_MAXSERV]; | |
const char *errstr; | |
char *tunmask, *lip, *lport, *tunip, *tundev, *rip, *rport; | |
socklen_t len; | |
int e, i, tund, sd, tunnum; | |
tunnum = -1; | |
tundev = NULL; | |
tunip = NULL; | |
tunmask = "255.255.255.0"; | |
tunsa = NULL; | |
tunmasksa = NULL; | |
lport = DEFAULTPORT; | |
lip = "0.0.0.0"; | |
while ((i = getopt(argc, argv, "VD:b:hm:p:t:v")) != -1) | |
switch (i) { | |
case 'V': | |
printf("version 0.0.1\n"); | |
exit(0); | |
case 'D': | |
tundev = optarg; | |
break; | |
case 'b': | |
lip = optarg; | |
break; | |
case 'h': | |
prusage(stdout); | |
exit(0); | |
case 'p': | |
lport = optarg; | |
break; | |
case 'v': | |
verbose++; | |
break; | |
case '?': | |
prusage(stderr); | |
exit(1); | |
default: | |
abort(); | |
} | |
argc -= optind; | |
argv += optind; | |
if (argc < 2 || argc > 3) { | |
prusage(stderr); | |
exit(1); | |
} | |
tunip = argv[0]; | |
rip = argv[1]; | |
if (argc == 3) | |
rport = argv[2]; | |
else | |
rport = DEFAULTPORT; | |
/* | |
* Validate server port and ip, tunnel device, ip and mask and remote | |
* address. | |
*/ | |
e = strtoaddr(rip, rport, &rsa); | |
if (e) | |
errx(1, "%s: remote %s:%s", gai_strerror(e), rip, rport); | |
strtonum(lport, 1, MAXPORT, &errstr); | |
if (errstr) | |
errx(1, "invalid port number %s", lport); | |
e = strtoaddr(lip, lport, &lsa); | |
if (e) | |
errx(1, "%s: server %s:%s", gai_strerror(e), lip, lport); | |
e = strtoaddr(tunip, NULL, &tunsa); | |
if (e) | |
errx(1, "%s: tunnel ip %s", gai_strerror(e), tunip); | |
e = strtoaddr(tunmask, NULL, &tunmasksa); | |
if (e) | |
errx(1, "%s: tunnel mask %s", gai_strerror(e), tunmask); | |
/* Determine tunnel device name. */ | |
if (tundev == NULL) { | |
tunnum = findtun(tunsa, &i); | |
if (tunnum == -1) | |
tunnum = i + 1; | |
/* Not really needed to free later on. */ | |
if (asprintf(&tundev, "tun%d", tunnum) < 4) | |
errx(1, "asprintf"); | |
} | |
/* Open and bring up tunnel. */ | |
if ((tund = opentunnel(tundev)) == -1) | |
err(1, "opening tunnel device: %s %s", tundev, tunip); | |
if (!tunhasaddr(tundev, tunsa)) | |
if (assignaddr(tundev, tunsa, tunmasksa) == -1) | |
err(1, "assignaddr %s %s", tundev, tunip); | |
/* | |
* Chroot and pledge. | |
*/ | |
if (chroot(EMPTYDIR) == -1 || chdir("/") == -1) | |
err(1, "%s: chroot %s", __func__, EMPTYDIR); | |
if (pledge("stdio inet proc", NULL) == -1) | |
err(1, "pledge"); | |
if (lport == NULL) | |
lport = ""; | |
/* Start udp server. */ | |
sd = bindserver(lsa, SOCK_DGRAM); | |
/* Connect to remote so that kernel bounds it's local address as well. */ | |
if (connect(sd, rsa, rsa->sa_len) == -1) | |
err(1, "connect"); | |
/* Print server info if verbose >= 0. */ | |
len = sizeof(ss); | |
if (getsockname(sd, (struct sockaddr *)&ss, &len) == -1) | |
err(1, "getsockname"); | |
e = addrtostr((const struct sockaddr *)&ss, aihost, aiserv); | |
if (e) | |
warnx("%s", gai_strerror(e)); | |
else if (verbose > -1) | |
fprintf(stderr, "%s %s ([%s]:%s <-> ", tundev, tunip, aihost, | |
aiserv); | |
len = sizeof(ss); | |
if (getpeername(sd, (struct sockaddr *)&ss, &len) == -1) | |
err(1, "getsockname"); | |
e = addrtostr((const struct sockaddr *)&ss, aihost, aiserv); | |
if (e) | |
warnx("%s", gai_strerror(e)); | |
else if (verbose > -1) | |
fprintf(stderr, "[%s]:%s)\n", aihost, aiserv); | |
len = sizeof(i); | |
if (getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &i, &len) == -1) | |
err(1, "getsockopt"); | |
if (verbose > 0) | |
warnx("receive buffer %d bytes", i); | |
/* | |
* Let one process forward from socket to interface, and another process | |
* the other way around. | |
*/ | |
switch (fork()) { | |
case -1: | |
err(1, "fork"); | |
case 0: | |
if (pledge("stdio", NULL) == -1) | |
err(1, "pledge"); | |
/* child: forward from socket to interface */ | |
fwddtod(sd, tund); | |
/* Never returns. */ | |
abort(); | |
default: | |
if (pledge("stdio", NULL) == -1) | |
err(1, "pledge"); | |
/* parent: forward from interface to socket */ | |
fwddtod(tund, sd); | |
/* Never returns. */ | |
abort(); | |
} | |
exit(1); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment