Skip to content

Instantly share code, notes, and snippets.

@timkuijsten
Created July 12, 2018 19:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timkuijsten/954f3f1e8e84980a930a1a6a591b96ee to your computer and use it in GitHub Desktop.
Save timkuijsten/954f3f1e8e84980a930a1a6a591b96ee to your computer and use it in GitHub Desktop.
unencrypted udp based vpn tunnel
/* 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