# HG changeset patch # User Ivo Smits # Date 1391812119 -3600 # Node ID 17cb7cdbb8beb425503560b87f4e8e996ef9d42e Working prototype diff -r 000000000000 -r 17cb7cdbb8be include.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include.h Fri Feb 07 23:28:39 2014 +0100 @@ -0,0 +1,90 @@ +/* Copyright 2014 Ivo Smits . All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are + permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + The views and conclusions contained in the software and documentation are those of the + authors and should not be interpreted as representing official policies, either expressed + or implied, of Ivo Smits.*/ + +#include + +typedef struct tunnel_context tunnel_context; +typedef struct connection_context connection_context; + +struct connection_context { + int recv_socket; + unsigned char recv_buffer[2000]; + int recv_offset; + bool recv_encrypted; + unsigned char recv_key[32]; + unsigned char recv_nonce[24]; + + int send_socket; + bool send_encrypted; + unsigned char send_key[32]; + unsigned char send_nonce[24]; + + unsigned char local_seckey_current[32]; + unsigned char local_seckey_next[32]; + unsigned char remote_pubkey[32]; + unsigned char nonce_next[24]; + + char* password; + + bool local_tunnelready; + bool remote_tunnelready; + bool key_updated; + + bool pong; + bool startcryptauthsent; + + unsigned char remote_pubkey_expect[32]; + bool require_key_authentication; + bool require_encryption; + bool require_password_authentication; + + tunnel_context* tunnel; +}; + +bool connection_init(connection_context* context); +bool connection_init_socket(connection_context* context, const int recvsocket, const int sendsocket); +bool connection_init_encryption(connection_context* context, const unsigned char* localseckey, const unsigned char* remotepubkey); +bool connection_init_passwordauth(connection_context* context, char* password); +bool connection_init_done(connection_context* context); +bool connection_update_key(connection_context* context); +bool connection_ping(connection_context* context); +bool connection_read(connection_context* context); +bool connection_write_data(connection_context* context, unsigned char* buffer, int len); + +struct tunnel_context { + int fd; + int fake_pi; + connection_context* connection; +}; + +bool tunnel_init(tunnel_context* context); +bool tunnel_read(tunnel_context* context); +bool tunnel_write_data(tunnel_context* tunnel, unsigned char* buffer, int len); + +extern char* (*getconf)(const char*); +int errorexit(const char* text); +int errorexitf(const char* text, const char* error); +bool errorexitp(const char* text); + diff -r 000000000000 -r 17cb7cdbb8be main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.c Fri Feb 07 23:28:39 2014 +0100 @@ -0,0 +1,222 @@ +/* Copyright 2014 Ivo Smits . All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are + permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + The views and conclusions contained in the software and documentation are those of the + authors and should not be interpreted as representing official policies, either expressed + or implied, of Ivo Smits.*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef HAVE_NETINET_IN_H +#include +#endif +#include +#include +#include "include.h" + +char* (*getconf)(const char*) = getenv; + +static void hex2bin(unsigned char* dest, const char* src, const int count) { + int i; + for (i = 0; i < count; i++) { + if (*src >= '0' && *src <= '9') *dest = *src - '0'; + else if (*src >= 'a' && * src <='f') *dest = *src - 'a' + 10; + else if (*src >= 'A' && * src <='F') *dest = *src - 'A' + 10; + src++; *dest = *dest << 4; + if (*src >= '0' && *src <= '9') *dest += *src - '0'; + else if (*src >= 'a' && *src <= 'f') *dest += *src - 'a' + 10; + else if (*src >= 'A' && *src <= 'F') *dest += *src - 'A' + 10; + src++; dest++; + } +} + +static bool crypto_init(connection_context* context, bool* keyupdate) { + unsigned char cpublickey[32], csecretkey[32]; + bool hpublickey = false, hsecretkey = false; + char* envval; + *keyupdate = false; + if ((envval = getconf("PUBLIC_KEY"))) { + if (strlen(envval) != 64) return errorexit("PUBLIC_KEY length"); + hex2bin(cpublickey, envval, 32); + hpublickey = true; + } + if ((envval = getconf("PRIVATE_KEY"))) { + if (strlen(envval) != 64) return errorexit("PRIVATE_KEY length"); + hex2bin(csecretkey, envval, 32); + hsecretkey = true; + } else if ((envval = getconf("PRIVATE_KEY_FILE"))) { + FILE* pkfile = fopen(envval, "rb"); + if (!pkfile) return errorexitp("Could not open PRIVATE_KEY_FILE"); + char pktextbuf[64]; + const size_t pktextsize = fread(pktextbuf, 1, sizeof(pktextbuf), pkfile); + if (pktextsize == 32) { + memcpy(csecretkey, pktextbuf, 32); + } else if (pktextsize == 64) { + hex2bin(csecretkey, pktextbuf, 32); + } else { + return errorexit("PRIVATE_KEY length"); + } + fclose(pkfile); + hsecretkey = true; + } + if (hpublickey || hsecretkey || ((envval = getconf("ENCRYPT")) && atoi(envval))) { + if (!hpublickey) fprintf(stderr, "Warning: encryption enabled but remote key not set, cryptographic authentication disabled.\n"); + if (!connection_init_encryption(context, hsecretkey ? csecretkey : NULL, hpublickey ? cpublickey : NULL)) return false; + *keyupdate = true; + } else { + fprintf(stderr, "Warning: encryption disabled.\n"); + } + if ((envval = getconf("PASSWORD"))) { + if (!connection_init_passwordauth(context, strdup(envval))) return false; + } + return true; +} + +typedef union { + struct sockaddr any; + struct sockaddr_in ip4; + struct sockaddr_in6 ip6; +} sockaddr_any; + +static int sockaddr_set_port(sockaddr_any* sa, int port) { + port = htons(port); + int af = sa->any.sa_family; + if (af == AF_INET) sa->ip4.sin_port = port; + else if (af == AF_INET6) sa->ip6.sin6_port = port; + else return errorexit("Unknown address family"); + return 0; +} + +static bool socket_init(connection_context* context) { + char* envval; + fprintf(stderr, "Initializing socket...\n"); + struct addrinfo *ai_local = NULL, *ai_remote = NULL; + unsigned short af = 0; + int ret; + if ((envval = getconf("LOCAL_ADDRESS"))) { + if ((ret = getaddrinfo(envval, NULL, NULL, &ai_local))) return errorexitf("getaddrinfo(LOCAL_ADDRESS)", gai_strerror(ret)); + if (!ai_local) return errorexit("LOCAL_ADDRESS lookup failed"); + if (ai_local->ai_addrlen > sizeof(sockaddr_any)) return errorexit("Resolved LOCAL_ADDRESS is too big"); + af = ai_local->ai_family; + } + if ((envval = getconf("REMOTE_ADDRESS"))) { + if ((ret = getaddrinfo(envval, NULL, NULL, &ai_remote))) return errorexitf("getaddrinfo(REMOTE_ADDRESS)", gai_strerror(ret)); + if (!ai_remote) return errorexit("REMOTE_ADDRESS lookup failed"); + if (ai_remote->ai_addrlen > sizeof(sockaddr_any)) return errorexit("Resolved REMOTE_ADDRESS is too big"); + if (af && af != ai_remote->ai_family) return errorexit("Address families do not match"); + af = ai_remote->ai_family; + } + if (!af) return connection_init_socket(context, 0, 1); + int sa_size = sizeof(sockaddr_any); + if (af == AF_INET) sa_size = sizeof(struct sockaddr_in); + else if (af == AF_INET6) sa_size = sizeof(struct sockaddr_in6); + int sfd = socket(af, SOCK_STREAM, IPPROTO_TCP); + if (sfd < 0) return errorexitp("Could not create socket"); + sockaddr_any udpaddr; + if (ai_local) { + memset(&udpaddr, 0, sizeof(udpaddr)); + udpaddr.any.sa_family = af; + memcpy(&udpaddr, ai_local->ai_addr, ai_local->ai_addrlen); + int port = 2998; + if ((envval = getconf("LOCAL_PORT"))) port = atoi(envval); + if (sockaddr_set_port(&udpaddr, port)) return -1; + if (bind(sfd, &udpaddr.any, sa_size)) return errorexitp("Could not bind socket"); + } + if (ai_remote) { + memset(&udpaddr, 0, sizeof(udpaddr)); + udpaddr.any.sa_family = af; + memcpy(&udpaddr, ai_remote->ai_addr, ai_remote->ai_addrlen); + int port = 2998; + if ((envval = getconf("REMOTE_PORT"))) port = atoi(envval); + if (sockaddr_set_port(&udpaddr, port)) return -1; + if (connect(sfd, &udpaddr.any, sa_size)) return errorexitp("Could not connect socket"); + } else { + return errorexit("REMOTE_ADDRESS not specified and server mode is currently not supported :-(. Please use (x)inetd or similar."); + } + if (ai_local) freeaddrinfo(ai_local); + if (ai_remote) freeaddrinfo(ai_remote); + return connection_init_socket(context, sfd, sfd); +} + +static bool mainA() { + connection_context context; + tunnel_context tunnel; + bool keyupdate = false; + if (!connection_init(&context)) return false; + if (!socket_init(&context)) return false; + if (!crypto_init(&context, &keyupdate)) return false; + if (!connection_init_done(&context)) return false; + while (!context.local_tunnelready) if (!connection_read(&context)) return false; + if (keyupdate) connection_update_key(&context); + + if (!tunnel_init(&tunnel)) return false; + context.tunnel = &tunnel; + tunnel.connection = &context; + + struct pollfd fds[2]; + fds[0].fd = context.recv_socket; + fds[0].events = POLLIN; + fds[1].fd = tunnel.fd; + fds[1].events = POLLIN; + + while (true) { + int len = poll(fds, 2, 10000); + if (len < 0) return errorexitp("poll failed"); + else if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) return errorexit("poll error on socket"); + else if (fds[1].revents & (POLLHUP | POLLNVAL)) return errorexit("poll error on tap device"); + if (len == 0) { + if (keyupdate) { + if (!context.key_updated) return errorexit("key update timed out"); + if (!connection_update_key(&context)) return false; + } else { + if (!connection_ping(&context)) return errorexit("ping timed out"); + } + } + if (fds[0].revents & POLLERR) return errorexitp("poll error on socket"); + if (fds[0].revents & POLLIN) if (!connection_read(&context)) return false; + if (fds[1].revents & POLLIN) if (!tunnel_read(&tunnel)) return false; + } + return -1; +} + +int main() { + return mainA() ? 0 : -1; +} + +int errorexit(const char* text) { + fprintf(stderr, "%s\n", text); + return false; +} +int errorexitf(const char* text, const char* error) { + fprintf(stderr, "%s: %s\n", text, error); + return false; +} +bool errorexitp(const char* text) { + perror(text); + return false; +} + diff -r 000000000000 -r 17cb7cdbb8be protocol.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/protocol.c Fri Feb 07 23:28:39 2014 +0100 @@ -0,0 +1,335 @@ +/* Copyright 2014 Ivo Smits . All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are + permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + The views and conclusions contained in the software and documentation are those of the + authors and should not be interpreted as representing official policies, either expressed + or implied, of Ivo Smits.*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "include.h" + +static unsigned char workbuffer[2000]; + +static int devurandomfd = -1; +static bool randombytes(unsigned char* buffer, int len) { + if (devurandomfd == -1) devurandomfd = open("/dev/urandom", O_RDONLY); + if (devurandomfd == -1) return errorexit("could not open /dev/urandom"); + while (len > 0) { + int got = read(devurandomfd, buffer, len); + if (got < 0) return errorexitp("could not read from /dev/urandom"); + buffer += got; + len -= got; + } + return true; +} + +static void dumphex(char* lbl, unsigned char* buffer, int len) { + fprintf(stderr, "%s: ", lbl); + for (; len > 0; len--, buffer++) fprintf(stderr, "%02x", *buffer); + fprintf(stderr, "\n"); +} + +static void nonceinc(unsigned char* nonce) { + int i; + for (i = 23; i >= 0 && (++nonce[i] == 0); i--); +} + +static bool send_all(int socket, unsigned char* buffer, int length) { + while (length) { + int sent = write(socket, buffer, length); + if (sent <= 0) return errorexitp("socket write failed"); + buffer += sent; + length -= sent; + } + return true; +} +static bool send_packet(connection_context* context, unsigned char* buffer, int length, bool controlflag) { + fprintf(stderr, "Send packet (size=%d type=%d control=%d encrypt=%d)\n", length, buffer[0], controlflag, context->send_encrypted); + if (context->send_encrypted) { + if (length + 32 > sizeof(workbuffer)) return errorexit("packet too big for encryption buffer"); + memmove(workbuffer + 32, buffer, length); + memset(workbuffer, 0, 32); + crypto_box_curve25519xsalsa20poly1305_afternm(workbuffer, workbuffer, length + 32, context->send_nonce, context->send_key); + nonceinc(context->send_nonce); + buffer = workbuffer + 16; + length += 16; + } + if (length > 0x7FFF) return errorexit("packet too big for framing"); + unsigned char lbuf[2]; + length = (length & 0x7FFF) | (controlflag ? 0x8000 : 0); + lbuf[0] = (length >> 8) & 0xFF; + lbuf[1] = length & 0xFF; + if (!send_all(context->send_socket, lbuf, 2)) return false; + if (!send_all(context->send_socket, buffer, length & 0x7FFF)) return false; + return true; +} +static bool send_command_ack(connection_context* context, unsigned char code) { + workbuffer[0] = code; + return send_packet(context, workbuffer, 1, true); +} + + +static bool check_tunnel_ready(connection_context* context) { + if (context->local_tunnelready) return true; + if (context->require_key_authentication) return true; + if (context->require_password_authentication) return true; + if (context->require_encryption && !context->recv_encrypted) return true; + if (!send_command_ack(context, 0)) return false; + context->local_tunnelready = true; + return true; +} + +static bool process_network_packet(connection_context* context, unsigned char* buffer, int pktlen, int controlflag); +bool connection_read(connection_context* context) { + int got = read(context->recv_socket, context->recv_buffer + context->recv_offset, sizeof(context->recv_buffer) - context->recv_offset); + if (got < 0) return errorexitp("read failure on socket"); + context->recv_offset += got; + while (context->recv_offset >= 2) { + int pktlen = ((unsigned char)context->recv_buffer[0] << 8) | (unsigned char)context->recv_buffer[1]; + int controlflag = pktlen & 0x8000; + pktlen &= 0x7FFF; + if (pktlen > sizeof(context->recv_buffer)) return errorexit("received packet too big for buffer"); + if (context->recv_offset < pktlen + 2) break; + if (!process_network_packet(context, context->recv_buffer + 2, pktlen, !!controlflag)) return false; + context->recv_offset -= 2 + pktlen; + memmove(context->recv_buffer, context->recv_buffer + 2 + pktlen, context->recv_offset); + } + if (!context->local_tunnelready) if (!check_tunnel_ready(context)) return false; + return true; +} +static bool send_start_crypt_auth(connection_context* context, const unsigned char* key) { + if (!context->key_updated) return context->startcryptauthsent && !key; + if (key) { + memcpy(context->local_seckey_next, key, 32); + } else { + randombytes(context->local_seckey_next, 32); + } + workbuffer[0] = 1; + crypto_scalarmult_curve25519_base(workbuffer + 1, context->local_seckey_next); + memcpy(workbuffer + 1 + 32, context->nonce_next, 24); + if (!send_packet(context, workbuffer, 1 + 32 + 24, true)) return false; + crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next); + memcpy(context->send_nonce + 12, context->nonce_next + 12, 12); + context->send_encrypted = true; + context->startcryptauthsent = true; + context->key_updated = false; + return true; +} +static bool begin_update_key(connection_context* context, char* seckey) { + if (!context->key_updated) return !seckey; + if (seckey) { + memcpy(context->local_seckey_next, seckey, 32); + } else { + randombytes(context->local_seckey_next, 32); + } + workbuffer[0] = 3; + crypto_scalarmult_curve25519_base(workbuffer + 1, context->local_seckey_next); + if (!send_packet(context, workbuffer, 1 + 32, true)) return false; + crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next); + context->send_encrypted = true; + context->key_updated = false; + return true; +} +static bool password_hash(unsigned char* output, unsigned char* salt, int saltlength, char* password) { + int pwlen = password ? strlen(password) : 0; + if (saltlength + pwlen > sizeof(workbuffer)) return errorexit("password and salt too big for buffer"); + if (saltlength) memmove(workbuffer, salt, saltlength); + if (pwlen) memcpy(workbuffer + saltlength, password, pwlen); + crypto_hash_sha512(output, workbuffer, saltlength + pwlen); + return true; +} +static bool process_network_packet(connection_context* context, unsigned char* buffer, int pktlen, int controlflag) { + if (context->recv_encrypted) { + if (pktlen + 16 > sizeof(workbuffer)) return errorexit("received packet too big for decrypt buffer"); + memmove(workbuffer + 16, buffer, pktlen); + memset(workbuffer, 0, 16); + if (crypto_box_curve25519xsalsa20poly1305_open_afternm(workbuffer, workbuffer, pktlen + 16, context->recv_nonce, context->recv_key)) return false; + nonceinc(context->recv_nonce); + buffer = workbuffer + 32; + pktlen -= 16; + } + if (controlflag) { + fprintf(stderr, "Received packet (size=%d type=%d control=%d encrypt=%d)\n", pktlen, buffer[0], controlflag, context->recv_encrypted); + if (pktlen < 1) return errorexit("zero length control packet"); + switch (buffer[0]) { + case 0: + fprintf(stderr, "Control: tunnel ready\n"); + context->remote_tunnelready = true; + break; + case 1: + fprintf(stderr, "Control: crypto auth req\n"); + if (pktlen < 1 + 32 + 24) return errorexit("short control packet 1"); + if (context->require_key_authentication) { + if (memcmp(buffer + 1, context->remote_pubkey_expect, 32)) return errorexit("incorrect crypto auth key"); + context->require_key_authentication = true; + } + if (!context->startcryptauthsent) send_start_crypt_auth(context, NULL); + if (!send_command_ack(context, 2)) return false; + memcpy(context->remote_pubkey, buffer + 1, 32); + memcpy(context->send_nonce, buffer + 1 + 32, 12); + memcpy(context->recv_nonce + 12, buffer + 1 + 32 + 12, 12); + crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current); + crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next); + context->recv_encrypted = context->send_encrypted = true; + break; + case 2: + fprintf(stderr, "Control: crypto auth ack\n"); + memcpy(context->local_seckey_current, context->local_seckey_next, 32); + memcpy(context->recv_nonce, context->nonce_next, 12); + crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current); + context->recv_encrypted = true; + context->key_updated = true; + begin_update_key(context, NULL); + break; + case 3: + fprintf(stderr, "Control: key update req\n"); + if (pktlen < 1 + 32) return errorexit("short control packet 3"); + if (!send_command_ack(context, 4)) return false; + memcpy(context->remote_pubkey, buffer + 1, 32); + crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current); + crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next); + context->recv_encrypted = context->send_encrypted = true; + break; + case 4: + fprintf(stderr, "Control: key update ack\n"); + memcpy(context->local_seckey_current, context->local_seckey_next, 32); + crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current); + context->recv_encrypted = true; + context->key_updated = true; + break; + case 5: + fprintf(stderr, "Control: password authentication req\n"); + unsigned char pwhash[64]; + if (!password_hash(pwhash, buffer + 1, pktlen - 1, context->password)) return false; + workbuffer[0] = 6; + memcpy(workbuffer + 1, pwhash, 64); + if (!send_packet(context, workbuffer, 1 + 64, true)) return false; + break; + case 6: + fprintf(stderr, "Control: password authentication ack\n"); + if (pktlen < 1 + 64) return errorexit("short control packet 6");; + char pwhashcheck[64]; + memcpy(pwhashcheck, buffer + 1, 64); + if (!password_hash(pwhash, context->nonce_next, 12, context->password)) return false; + if (memcmp(pwhash, pwhashcheck, 64)) return errorexit("incorrect password auth");; + context->require_password_authentication = false; + break; + case 7: + fprintf(stderr, "Control: disable encryption req\n"); + context->recv_encrypted = context->send_encrypted = false; + if (!send_command_ack(context, 8)) return false; + break; + case 8: + fprintf(stderr, "Control: disable encryption ack\n"); + context->recv_encrypted = false; + break; + case 9: + fprintf(stderr, "Control: echo req\n"); + if (pktlen > sizeof(workbuffer)) return errorexit("echo request too big");; + memmove(workbuffer, buffer, pktlen); + workbuffer[0] = 10; + if (!send_packet(context, workbuffer, pktlen, true)) return false; + break; + case 10: + fprintf(stderr, "Control: echo resp\n"); + context->pong = true; + break; + case 13: + if (!context->local_tunnelready || !context->remote_tunnelready) return errorexit("received data packet while tunnel not ready");; + if (context->tunnel && !tunnel_write_data(context->tunnel, buffer + 1, pktlen - 1)) return false; + break; + case 11: + case 12: + case 17: + case 81: + break; + default: + fprintf(stderr, "Unknown control type %d\n", buffer[0]); + break; + } + } else { + if (!context->local_tunnelready || !context->remote_tunnelready) return errorexit("received data packet while tunnel not ready");; + fprintf(stderr, "Tunnel data %d\n", pktlen); + if (context->tunnel && !tunnel_write_data(context->tunnel, buffer, pktlen)) return false; + } + return true; +} + +bool connection_ping(connection_context* context) { + if (!context->pong) return false; + context->pong = false; + send_command_ack(context, 9); + return true; +} + +bool connection_init(connection_context* context) { + memset(context, 0, sizeof(connection_context)); + char localpubkey[32]; + crypto_scalarmult_curve25519_base(context->remote_pubkey, context->local_seckey_current); + randombytes(context->nonce_next, 24); + context->key_updated = true; + context->pong = true; + return true; +} +bool connection_init_socket(connection_context* context, const int recvsocket, const int sendsocket) { + context->recv_socket = recvsocket; + context->send_socket = sendsocket; + if (!send_packet(context, (unsigned char*)"QUICKTUN", 8, true)) return false; + return true; +} +bool connection_init_encryption(connection_context* context, const unsigned char* localseckey, const unsigned char* remotepubkey) { + if (remotepubkey) { + memcpy(context->remote_pubkey_expect, remotepubkey, 32); + context->require_key_authentication = true; + } + context->require_encryption = true; + if (!send_start_crypt_auth(context, localseckey)) return false; + return true; +} +bool connection_init_passwordauth(connection_context* context, char* password) { + context->password = password; + context->require_password_authentication = true; + workbuffer[0] = 5; + memcpy(workbuffer + 1, context->nonce_next, 12); + if (!send_packet(context, workbuffer, 1 + 12, true)) return false; + return true; +} + +bool connection_write_data(connection_context* context, unsigned char* buffer, int len) { + return send_packet(context, buffer, len, false); +} + +bool connection_init_done(connection_context* context) { + return check_tunnel_ready(context); +} + +bool connection_update_key(connection_context* context) { + return begin_update_key(context, NULL); +} + diff -r 000000000000 -r 17cb7cdbb8be tunnel.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tunnel.c Fri Feb 07 23:28:39 2014 +0100 @@ -0,0 +1,134 @@ +/* Copyright 2014 Ivo Smits . All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are + permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + The views and conclusions contained in the software and documentation are those of the + authors and should not be interpreted as representing official policies, either expressed + or implied, of Ivo Smits.*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef linux + #include + #include +#else + #define ETH_FRAME_LEN 1514 + #include + #ifdef SOLARIS + #include + #include + #endif +#endif +#include "include.h" + +static unsigned char readbuffer[2000]; + +bool tunnel_write_data(tunnel_context* context, unsigned char* buffer, int len) { + if (context->fake_pi) { + if (len == 0) return true; + int ipver = (*buffer >> 4) & 0xf; + int pihdr = 0; +#if defined linux + if (ipver == 4) pihdr = 0x0000 | (0x0008 << 16); //little endian: flags and protocol are swapped + else if (ipver == 6) pihdr = 0x0000 | (0xdd86 << 16); +#else + if (ipver == 4) pihdr = htonl(AF_INET); + else if (ipver == 6) pihdr = htonl(AF_INET6); +#endif + struct iovec iov[2]; + iov[0].iov_base = &pihdr; + iov[0].iov_len = sizeof(pihdr); + iov[1].iov_base = buffer; + iov[1].iov_len = len; + writev(context->fd, iov, 2); + } else { + write(context->fd, buffer, len); + } + return true; +} + +bool tunnel_read(tunnel_context* context) { + int len = read(context->fd, readbuffer, sizeof(readbuffer)); + if (len < 0) return errorexitp("read failure on tap device"); + if (!context->connection) return true; + if (context->fake_pi) { + if (len < 4) return errorexit("short packet received from tap device"); + connection_write_data(context->connection, readbuffer + 4, len - 4); + } else { + connection_write_data(context->connection, readbuffer, len); + } + return true; +} + +bool tunnel_init(tunnel_context* context) { + memset(context, 0, sizeof(tunnel_context)); + char* envval; + fprintf(stderr, "Initializing tun/tap device...\n"); + int ttfd; //Tap device file descriptor + int tunmode = 0; + if ((envval = getconf("TUN_MODE"))) tunmode = atoi(envval); +#if defined(__linux__) + struct ifreq ifr; //required for tun/tap setup + memset(&ifr, 0, sizeof(ifr)); + if ((ttfd = open("/dev/net/tun", O_RDWR)) < 0) return errorexitp("Could not open tun/tap device file"); + if ((envval = getconf("INTERFACE"))) strcpy(ifr.ifr_name, envval); + ifr.ifr_flags = tunmode ? IFF_TUN : IFF_TAP; + ifr.ifr_flags |= IFF_NO_PI; + if (ioctl(ttfd, TUNSETIFF, (void *)&ifr) < 0) return errorexitp("TUNSETIFF ioctl failed"); +#elif defined SOLARIS + int ip_fd = -1, if_fd = -1, ppa = 0; + if ((ttfd = open("/dev/tun", O_RDWR)) < 0) return errorexitp("Could not open tun device file"); + if ((ip_fd = open("/dev/ip", O_RDWR, 0)) < 0) return errorexitp("Could not open /dev/ip"); + if ((envval = getconf("INTERFACE"))) { + while (*envval && !isdigit((int)*envval)) envval++; + ppa = atoi(envval); + } + if ((ppa = ioctl(ttfd, TUNNEWPPA, ppa)) < 0) return errorexitp("Could not assign new PPA"); + if ((if_fd = open("/dev/tun", O_RDWR, 0)) < 0) return errorexitp("Could not open tun device file again"); + if (ioctl(if_fd, I_PUSH, "ip") < 0) return errorexitp("Could not push IP module"); + if (ioctl(if_fd, IF_UNITSEL, (char *)&ppa) < 0) return errorexitp("Could not set PPA"); + if (ioctl(ip_fd, I_LINK, if_fd) < 0) return errorexitp("Could not link TUN device to IP"); +#else + if (!(envval = getconf("INTERFACE"))) envval = "/dev/tun0"; + if ((ttfd = open(envval, O_RDWR)) < 0) return errorexitp("Could not open tun device file"); + if (tunmode) { + int i = IFF_POINTOPOINT | IFF_MULTICAST; + ioctl(ttfd, TUNSIFMODE, &i); +#if defined(__OpenBSD__) + context->fake_pi = true; +#else + i = 1; + ioctl(ttfd, TUNSIFHEAD, &i); +#endif + } +#endif + if ((envval = getconf("TUN_UP_SCRIPT"))) system(envval); + context->fd = ttfd; + return true; +} +