stun

simple point to point tunnel
git clone git://git.2f30.org/stun.git
Log | Files | Refs | README

commit 7c94056263faf3a17ef80ea333143b09fc2178b3
Author: sin <sin@2f30.org>
Date:   Thu Mar 17 18:36:28 +0000

Initial commit

Diffstat:
arg.h | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
stun.c | 375+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 440 insertions(+), 0 deletions(-)
diff --git a/arg.h b/arg.h @@ -0,0 +1,65 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles obsolete -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX)) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define LNGARG() &argv[0][0] + +#endif diff --git a/stun.c b/stun.c @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2016 Dimitris Papastamos <sin@2f30.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/if_tun.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/aes.h> +#include <openssl/evp.h> + +#include "arg.h" + +#define HDRLEN 4 +#define MTU 1440 + +EVP_CIPHER_CTX enc, dec; +char *argv0; +char *tundev; +char *host; +int port = 12080; +int debug; +int sflag; + +int +aesinit(unsigned char *pw, int pwlen, unsigned char *salt, + EVP_CIPHER_CTX *ectx, EVP_CIPHER_CTX *dctx) +{ + unsigned char key[32], iv[32]; + int ret, nrounds = 5; + + ret = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), + salt, pw, pwlen, nrounds, key, iv); + if (ret != 32) + errx(1, "wrong key size: %d\n", ret); + EVP_CIPHER_CTX_init(ectx); + EVP_EncryptInit_ex(ectx, EVP_aes_256_cbc(), NULL, key, iv); + EVP_CIPHER_CTX_init(dctx); + EVP_DecryptInit_ex(dctx, EVP_aes_256_cbc(), NULL, key, iv); + return 0; +} + +int +aesenc(EVP_CIPHER_CTX *ectx, unsigned char *ciphertext, + unsigned char *plaintext, int len) +{ + int clen = len + AES_BLOCK_SIZE, flen = 0; + + EVP_EncryptInit_ex(ectx, NULL, NULL, NULL, NULL); + EVP_EncryptUpdate(ectx, ciphertext, &clen, plaintext, len); + EVP_EncryptFinal_ex(ectx, ciphertext + clen, &flen); + return clen + flen; +} + +int +aesdec(EVP_CIPHER_CTX *ectx, unsigned char *plaintext, + unsigned char *ciphertext, int len) +{ + int plen = len, flen = 0; + + EVP_DecryptInit_ex(ectx, NULL, NULL, NULL, NULL); + EVP_DecryptUpdate(ectx, plaintext, &plen, ciphertext, len); + EVP_DecryptFinal_ex(ectx, plaintext + plen, &flen); + return plen + flen; +} + +int +opentun(char *tundev) +{ + struct tuninfo ti; + int fd; + int mode = IFF_UP | IFF_POINTOPOINT; + + fd = open(tundev, O_RDWR); + if (fd < 0) + err(1, "open %s", tundev); + if (ioctl(fd, TUNSIFMODE, &mode) < 0) + err(1, "ioctl: TUNSIFMODE %s", tundev); + if (ioctl(fd, TUNGIFINFO, &ti) < 0) + err(1, "ioctl: TUNGIFINFO %s", tundev); + ti.mtu = MTU; + if (ioctl(fd, TUNSIFINFO, &ti) < 0) + err(1, "ioctl: TUNSIFINFO %s", tundev); + return fd; +} + +ssize_t +writetun(int fd, char *buf, size_t len) +{ + struct iovec iov[2]; + uint32_t type = htonl(AF_INET); + ssize_t n; + + iov[0].iov_base = &type; + iov[0].iov_len = sizeof(type); + iov[1].iov_base = buf; + iov[1].iov_len = len; + n = writev(fd, iov, 2); + if (n < 0) + err(1, "writev"); + else if (n > 0) + return n - sizeof(type); + return n; +} + +ssize_t +readtun(int fd, char *buf, size_t len) +{ + struct iovec iov[2]; + uint32_t type; + ssize_t n; + + iov[0].iov_base = &type; + iov[0].iov_len = sizeof(type); + iov[1].iov_base = buf; + iov[1].iov_len = len; + n = readv(fd, iov, 2); + if (n < 0) + err(1, "readv"); + else if (n > 0) + return n - sizeof(type); + return n; +} + +void +packint(unsigned char *buf, int n) +{ + buf[0] = n >> 24 & 0xff; + buf[1] = n >> 16 & 0xff; + buf[2] = n >> 8 & 0xff; + buf[3] = n & 0xff; +} + +int +unpackint(unsigned char *buf) +{ + return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; +} + +/* handles partial writes */ +int +writeall(int fd, void *buf, int count) +{ + unsigned char *p = buf; + int n, total = 0; + + while (count > 0) { + n = write(fd, p + total, count); + if (n < 0) { + warn("write"); + total = -1; + break; + } else if (n == 0) { + break; + } + total += n; + count -= n; + } + return total; +} + +/* encrypt a packet and send it to the remote peer */ +int +writenet(int fd, unsigned char *buf, int buflen) +{ + unsigned char encbuf[MTU + AES_BLOCK_SIZE + HDRLEN]; + int pktlen; + + pktlen = aesenc(&enc, &encbuf[HDRLEN], buf, buflen); + packint(encbuf, pktlen); + pktlen += HDRLEN; + return writeall(fd, encbuf, pktlen); +} + +/* handles partial reads */ +int +readall(int fd, void *buf, int count) +{ + unsigned char *p = buf; + int n, total = 0; + + while (count > 0) { + n = read(fd, p + total, count); + if (n < 0) { + warn("read"); + total = -1; + break; + } else if (n == 0) { + break; + } + total += n; + count -= n; + } + return total; +} + +/* read a packet from the remote peer, decrypt it and pass it back through buf */ +int +readnet(int fd, unsigned char *buf, int count) +{ + unsigned char decbuf[MTU + AES_BLOCK_SIZE + HDRLEN]; + unsigned char hdr[HDRLEN]; + int n, pktlen; + + n = readall(fd, hdr, sizeof(hdr)); + if (n <= 0) + return n; + pktlen = unpackint(hdr); + if (pktlen < 0 || pktlen > MTU + AES_BLOCK_SIZE) { + warnx("bogus payload length: %d", pktlen); + return -1; + } + n = readall(fd, buf, pktlen); + if (n <= 0) + return n; + pktlen = aesdec(&dec, decbuf, buf, pktlen); + memcpy(buf, decbuf, pktlen); + return pktlen; +} + +int +loop(int netfd, int tunfd) +{ + unsigned char buf[MTU + AES_BLOCK_SIZE + HDRLEN]; + struct pollfd pfd[2]; + int ret, n; + + pfd[0].fd = netfd; + pfd[0].events = POLLIN; + pfd[1].fd = tunfd; + pfd[1].events = POLLIN; + while (1) { + ret = poll(pfd, 2, -1); + if (ret < 0) + err(1, "poll"); + if (pfd[0].revents & (POLLERR | POLLNVAL) || + pfd[1].revents & (POLLERR | POLLNVAL)) + errx(1, "bad fd"); + if (pfd[1].revents & (POLLIN | POLLHUP)) + if ((n = readtun(tunfd, buf, MTU)) <= 0 || + (n = writenet(netfd, buf, n)) <= 0) + return -1; + if (pfd[0].revents & (POLLIN | POLLHUP)) + if ((n = readnet(netfd, buf, MTU)) <= 0 || + (n = writetun(tunfd, buf, n)) <= 0) + return -1; + } + return 0; /* unreachable */ +} + +void +usage(void) +{ + fprintf(stderr, "usage: %s [-ds] [-h host] [-p port] interface\n", argv0); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_in local, remote; + char *pw; + const char *errstr; + int tunfd, netfd = -1, listenfd; + int ret; + + ARGBEGIN { + case 'd': + debug = 1; + break; + case 's': + sflag = 1; + break; + case 'h': + host = EARGF(usage()); + break; + case 'p': + port = strtonum(EARGF(usage()), 1, 65535, &errstr); + if (errstr) + errx(1, "invalid port number"); + break; + default: + usage(); + } ARGEND + + if (argc != 1) usage(); + tundev = argv[0]; + + tunfd = opentun(tundev); + + pw = getenv("STUNPW"); + if (!pw) + errx(1, "STUNPW is not set"); + if (aesinit(pw, strlen(pw), NULL, &enc, &dec) < 0) + errx(1, "couldn't initialize AES cipher"); + explicit_bzero(pw, strlen(pw)); + + if (sflag) { + /* server */ + listenfd = socket(AF_INET, SOCK_STREAM, 0); + if (listenfd < 0) + err(1, "socket"); + setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (int []){1}, sizeof(int)); + memset(&local, 0, sizeof(local)); + local.sin_family = AF_INET; + local.sin_addr.s_addr = htonl(INADDR_ANY); + local.sin_port = htons(port); + ret = bind(listenfd, (struct sockaddr *)&local, sizeof(local)); + if (ret < 0) + err(1, "bind"); + ret = listen(listenfd, 5); + if (ret < 0) + err(1, "listen"); + } else { + /* client */ + if (!host) + usage(); + netfd = socket(AF_INET, SOCK_STREAM, 0); + if (netfd < 0) + err(1, "socket"); + memset(&remote, 0, sizeof(remote)); + remote.sin_family = AF_INET; + remote.sin_addr.s_addr = inet_addr(host); + remote.sin_port = htons(port); + ret = connect(netfd, (struct sockaddr *)&remote, sizeof(remote)); + if (ret < 0) + err(1, "connect"); + } + +again: + if (sflag) { + if (netfd != -1) + close(netfd); + netfd = accept(listenfd, (struct sockaddr *)&remote, + (int []){sizeof(remote)}); + if (ret < 0) + err(1, "accept"); + if (debug) + printf("client connected\n"); + } + setsockopt(netfd, IPPROTO_TCP, TCP_NODELAY, (int []){1}, sizeof(int)); + if (loop(netfd, tunfd) < 0 && sflag) + goto again; + warnx("connection dropped"); + exit(1); +}