commit c5f00d34434dfeefb9eae11d9436e313b03c5888
parent 49e97ae6f81c498260780c13d5c15d45aad64588
Author: Joris Vink <joris@coders.se>
Date: Wed, 17 Sep 2014 08:53:13 +0200
Add KTunnel example, an anything-over-HTTPS tunnel using Kore.
Diffstat:
6 files changed, 927 insertions(+), 0 deletions(-)
diff --git a/examples/ktunnel/.gitignore b/examples/ktunnel/.gitignore
@@ -0,0 +1,5 @@
+*.o
+.objs
+ktunnel.so
+assets.h
+cert
diff --git a/examples/ktunnel/README.md b/examples/ktunnel/README.md
@@ -0,0 +1,35 @@
+KTunnel (anything over HTTPS)
+
+This example demonstrates how we can use Kore to create an
+anything-over-HTTPS tunnel.
+
+Build:
+```
+ # kore build
+```
+
+Run:
+```
+ # kore run
+```
+
+Test:
+```
+ # openssl s_client -connect 127.0.0.1:8888
+
+ Then enter:
+
+ GET /connect?host=74.125.232.248&port=80 HTTP/1.1
+ Host: 127.0.0.1
+
+ GET / HTTP/1.1
+ Host: www.google.se
+
+ (And hit enter)
+```
+
+You should see Kore connect to the google server given and
+return the results back to you.
+
+A client for OSX exists under the **client/** directory. It requires
+you to link with -lssl and -lcrypto.
diff --git a/examples/ktunnel/client/Makefile b/examples/ktunnel/client/Makefile
@@ -0,0 +1,16 @@
+#
+# You probably want to change the include and library
+# paths before compiling.
+#
+
+CFLAGS+=-Wall -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+=-Wmissing-declarations -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+=-Wsign-compare -Iincludes -g
+CFLAGS+=-I../../openssl-1.0.1i/include
+
+all:
+ gcc $(CFLAGS) -c client.c -o client.o
+ gcc -L../../openssl-1.0.1i/ client.o -o client -lcrypto -lssl
+
+clean:
+ rm -f client *.o
diff --git a/examples/ktunnel/client/client.c b/examples/ktunnel/client/client.c
@@ -0,0 +1,656 @@
+/*
+ * Copyright (c) 2014 Joris Vink <joris@coders.se>
+ *
+ * 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/param.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <sys/event.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "openssl/err.h"
+#include "openssl/ssl.h"
+
+#include <err.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define errno_s strerror(errno)
+#define ssl_errno_s ERR_error_string(ERR_get_error(), NULL)
+
+#define KQUEUE_EVENT_COUNT 100
+#define NETBUF_RECV_MAX 8192
+
+#define HTTP_REQUEST_FMT \
+ "GET /?host=%s&port=%s HTTP/1.1\r\nHost: %s\r\n\r\n"
+
+struct netbuf {
+ u_int8_t *data;
+ u_int32_t offset;
+ u_int32_t length;
+
+ TAILQ_ENTRY(netbuf) list;
+};
+
+TAILQ_HEAD(netbuf_list, netbuf);
+
+#define PEER_CAN_READ 0x01
+#define PEER_CAN_WRITE 0x02
+
+struct peer {
+ int fd;
+ int family;
+ int flags;
+
+ char *name;
+ char *host;
+ char *port;
+
+ int (*write)(struct peer *);
+ int (*read)(struct peer *, struct peer *);
+
+ SSL *ssl;
+ SSL_CTX *ssl_ctx;
+ void *connection;
+ struct peer *opposite;
+
+ struct netbuf *recv_buf;
+ struct netbuf_list write_queue;
+};
+
+#define CONNECTION_WILL_DISCONNECT 0x01
+
+struct connection {
+ int flags;
+ struct peer local;
+ struct peer remote;
+ TAILQ_ENTRY(connection) list;
+};
+
+TAILQ_HEAD(, connection) clients;
+TAILQ_HEAD(, connection) disconnects;
+
+void usage(void);
+void fatal(const char *, ...);
+
+int ktunnel_peer_handle(struct peer *);
+void ktunnel_peer_cleanup(struct peer *);
+void ktunnel_connection_close(struct connection *);
+void ktunnel_connection_cleanup(struct connection *);
+
+void ktunnel_event_schedule(int, int, int, void *);
+
+void ktunnel_set_nonblock(int);
+int ktunnel_write_local(struct peer *);
+int ktunnel_write_remote(struct peer *);
+int ktunnel_read_local(struct peer *, struct peer *);
+int ktunnel_read_remote(struct peer *, struct peer *);
+
+void ktunnel_accept(struct peer *);
+void ktunnel_bind(struct peer *, struct addrinfo *);
+void ktunnel_connect(struct peer *, struct addrinfo *);
+void ktunnel_peer_init(struct peer *, const char *,
+ void (*cb)(struct peer *, struct addrinfo *));
+
+void ktunnel_netbuf_create(struct netbuf **, struct netbuf_list *,
+ u_int8_t *, u_int32_t);
+
+int kfd = - 1;
+u_int32_t nchanges = 0;
+struct kevent *events = NULL;
+struct kevent *changelist = NULL;
+char *target_host = NULL;
+char *target_port = NULL;
+char *remote_name = NULL;
+
+void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: ktunnel-client local:port remote:port target:port\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int n, i;
+ struct connection *c, *cnext;
+ struct peer lpeer, *peer;
+
+ if (argc != 4)
+ usage();
+
+ TAILQ_INIT(&clients);
+ TAILQ_INIT(&disconnects);
+
+ if ((kfd = kqueue()) == -1)
+ fatal("kqueue(): %s", errno_s);
+
+ nchanges = 0;
+ events = calloc(KQUEUE_EVENT_COUNT, sizeof(struct kevent));
+ changelist = calloc(KQUEUE_EVENT_COUNT, sizeof(struct kevent));
+ if (events == NULL || changelist == NULL)
+ fatal("calloc(): %s", errno_s);
+
+ memset(&lpeer, 0, sizeof(lpeer));
+ ktunnel_peer_init(&lpeer, argv[1], ktunnel_bind);
+ ktunnel_event_schedule(lpeer.fd, EVFILT_READ, EV_ADD, &lpeer);
+
+ remote_name = argv[2];
+ target_host = argv[3];
+
+ if ((target_port = strchr(target_host, ':')) == NULL)
+ fatal("Target host does not contain a port");
+ *(target_port)++ = '\0';
+
+ for (;;) {
+ n = kevent(kfd, changelist, nchanges,
+ events, KQUEUE_EVENT_COUNT, NULL);
+ if (n == -1) {
+ if (errno == EINTR)
+ continue;
+ fatal("kevent(): %s", errno_s);
+ }
+
+ nchanges = 0;
+ for (i = 0; i < n; i++) {
+ if (events[i].udata == NULL)
+ fatal("events[%d].udata == NULL", i);
+
+ peer = (struct peer *)events[i].udata;
+
+ if (events[i].flags & EV_EOF ||
+ events[i].flags & EV_ERROR) {
+ if (peer->fd == lpeer.fd)
+ fatal("error on listening socket");
+
+ ktunnel_connection_close(peer->connection);
+ continue;
+ }
+
+ if (peer->fd == lpeer.fd) {
+ ktunnel_accept(peer);
+ continue;
+ }
+
+ if (events[i].filter == EVFILT_READ)
+ peer->flags |= PEER_CAN_READ;
+ if (events[i].filter == EVFILT_WRITE)
+ peer->flags |= PEER_CAN_WRITE;
+
+ if (ktunnel_peer_handle(peer) == -1) {
+ ktunnel_connection_close(peer->connection);
+ } else {
+ if (!TAILQ_EMPTY(&peer->write_queue)) {
+ ktunnel_event_schedule(peer->fd,
+ EVFILT_WRITE,
+ EV_ADD | EV_ONESHOT, peer);
+ }
+ }
+ }
+
+ for (c = TAILQ_FIRST(&disconnects); c != NULL; c = cnext) {
+ cnext = TAILQ_NEXT(c, list);
+ TAILQ_REMOVE(&disconnects, c, list);
+ ktunnel_connection_cleanup(c);
+ }
+ }
+
+ return (0);
+}
+
+void
+ktunnel_peer_init(struct peer *peer, const char *name, void (*cb)(struct peer *,
+ struct addrinfo *))
+{
+ int r;
+ struct addrinfo *ai, *results;
+
+ if ((peer->name = strdup(name)) == NULL)
+ fatal("strdup() messed up");
+
+ peer->host = peer->name;
+ if ((peer->port = strchr(peer->host, ':')) == NULL)
+ fatal("No port section in given local host '%s'", peer->name);
+ *(peer->port)++ = '\0';
+
+ r = getaddrinfo(peer->host, peer->port, NULL, &results);
+ if (r != 0)
+ fatal("%s: %s", name, gai_strerror(r));
+
+ for (ai = results; ai != NULL; ai = ai->ai_next) {
+ if (ai->ai_socktype != SOCK_STREAM)
+ continue;
+ if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
+ continue;
+
+ cb(peer, ai);
+ peer->family = ai->ai_family;
+
+ break;
+ }
+
+ freeaddrinfo(results);
+}
+
+void
+ktunnel_accept(struct peer *peer)
+{
+ int fd;
+ struct connection *c;
+ struct sockaddr_in sin4;
+ struct sockaddr_in6 sin6;
+ struct sockaddr *sin;
+ socklen_t slen;
+
+ sin = NULL;
+
+ switch (peer->family) {
+ case AF_INET:
+ sin = (struct sockaddr *)&sin4;
+ slen = sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ sin = (struct sockaddr *)&sin6;
+ slen = sizeof(struct sockaddr_in6);
+ break;
+ default:
+ fatal("Unknown peer family %d", peer->family);
+ /* NOTREACHED */
+ }
+
+ if ((fd = accept(peer->fd, sin, &slen)) == -1)
+ fatal("accept(): %s", errno_s);
+
+ if ((c = malloc(sizeof(*c))) == NULL)
+ fatal("malloc(): %s", errno_s);
+
+ memset(c, 0, sizeof(*c));
+ c->local.fd = fd;
+ TAILQ_INIT(&c->local.write_queue);
+
+ ktunnel_event_schedule(c->local.fd, EVFILT_READ, EV_ADD, &c->local);
+ ktunnel_event_schedule(c->local.fd, EVFILT_WRITE,
+ EV_ADD | EV_ONESHOT, &c->local);
+
+ c->local.connection = c;
+ c->local.opposite = &c->remote;
+ c->local.read = ktunnel_read_local;
+ c->local.write = ktunnel_write_local;
+
+ c->remote.connection = c;
+ c->remote.opposite = &c->local;
+ c->remote.read = ktunnel_read_remote;
+ c->remote.write = ktunnel_write_remote;
+
+ ktunnel_peer_init(&c->remote, remote_name, ktunnel_connect);
+ ktunnel_netbuf_create(&c->local.recv_buf,
+ NULL, NULL, NETBUF_RECV_MAX);
+
+ ktunnel_set_nonblock(c->local.fd);
+ ktunnel_set_nonblock(c->remote.fd);
+
+ TAILQ_INSERT_TAIL(&clients, c, list);
+
+ printf("new connection %p (%p<->%p)\n", c, &c->local, &c->remote);
+}
+
+void
+ktunnel_bind(struct peer *peer, struct addrinfo *ai)
+{
+ if ((peer->fd = socket(ai->ai_family, ai->ai_socktype, 0)) == -1)
+ fatal("socket(): %s", errno_s);
+
+ if (bind(peer->fd, ai->ai_addr, ai->ai_addrlen) == -1) {
+ fatal("Cannot bind to %s:%s: %s",
+ peer->host, peer->port, errno_s);
+ }
+
+ if (listen(peer->fd, 10) == -1)
+ fatal("Cannot listen on socket: %s", errno_s);
+
+ TAILQ_INIT(&peer->write_queue);
+ ktunnel_netbuf_create(&peer->recv_buf, NULL, NULL, NETBUF_RECV_MAX);
+}
+
+void
+ktunnel_connect(struct peer *peer, struct addrinfo *ai)
+{
+ int l;
+ char *req;
+
+ if ((peer->fd = socket(ai->ai_family, ai->ai_socktype, 0)) == -1)
+ fatal("socket(): %s", errno_s);
+
+ if (connect(peer->fd, ai->ai_addr, ai->ai_addrlen) == -1) {
+ fatal("Cannot connect to %s:%s: %s",
+ peer->host, peer->port, errno_s);
+ }
+
+ TAILQ_INIT(&peer->write_queue);
+ ktunnel_netbuf_create(&peer->recv_buf, NULL, NULL, NETBUF_RECV_MAX);
+
+ /*
+ * XXX
+ * - Make this TLSv1.2 only
+ * - Add our client certs
+ * - Verify server cert properly
+ * - ...
+ */
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ if ((peer->ssl_ctx = SSL_CTX_new(SSLv3_method())) == NULL)
+ fatal("SSL_CTX_new(): %s", ssl_errno_s);
+
+ SSL_CTX_set_mode(peer->ssl_ctx, SSL_MODE_AUTO_RETRY);
+ if ((peer->ssl = SSL_new(peer->ssl_ctx)) == NULL)
+ fatal("SSL_new(): %s", ssl_errno_s);
+ if (!SSL_set_fd(peer->ssl, peer->fd))
+ fatal("SSL_set_fd(): %s", ssl_errno_s);
+ if (!SSL_connect(peer->ssl)) {
+ fatal("Could not establish an SSL connection to %s: %s",
+ peer->host, ssl_errno_s);
+ }
+
+ /* Send custom HTTP command. */
+ l = asprintf(&req, HTTP_REQUEST_FMT,
+ target_host, target_port, peer->host);
+ if (l == -1)
+ fatal("asprintf(): %s", errno_s);
+
+ if (SSL_write(peer->ssl, req, l) != l)
+ fatal("Failed to talk to %s:%s", peer->host, peer->port);
+
+ free(req);
+
+ ktunnel_event_schedule(peer->fd, EVFILT_READ, EV_ADD, peer);
+ ktunnel_event_schedule(peer->fd, EVFILT_WRITE,
+ EV_ADD | EV_ONESHOT, peer);
+
+ printf("Connected over SSL to %s:%s\n", peer->host, peer->port);
+}
+
+int
+ktunnel_peer_handle(struct peer *peer)
+{
+ int r;
+
+ printf("handling peer %p (%d)\n", peer, peer->flags);
+
+ if (peer->flags & PEER_CAN_READ) {
+ printf("\treading\n");
+ r = peer->read(peer, peer->opposite);
+ }
+
+ if (peer->flags & PEER_CAN_WRITE) {
+ printf("\twriting\n");
+ r = peer->write(peer);
+ }
+
+ return (r);
+}
+
+void
+ktunnel_connection_close(struct connection *c)
+{
+ printf("ktunnel_connection_close(%p)\n", c);
+
+ if (!(c->flags & CONNECTION_WILL_DISCONNECT)) {
+ c->flags |= CONNECTION_WILL_DISCONNECT;
+
+ TAILQ_REMOVE(&clients, c, list);
+ TAILQ_INSERT_TAIL(&disconnects, c, list);
+ }
+}
+
+void
+ktunnel_connection_cleanup(struct connection *c)
+{
+ ktunnel_peer_cleanup(&c->local);
+ ktunnel_peer_cleanup(&c->remote);
+
+ free(c);
+}
+
+void
+ktunnel_peer_cleanup(struct peer *peer)
+{
+ struct netbuf *nb, *next;
+
+ printf("ktunnel_peer_cleanup(%p)\n", peer);
+
+ close(peer->fd);
+
+ if (peer->ssl != NULL)
+ SSL_free(peer->ssl);
+ if (peer->ssl_ctx != NULL)
+ SSL_CTX_free(peer->ssl_ctx);
+
+ for (nb = TAILQ_FIRST(&peer->write_queue); nb != NULL; nb = next) {
+ next = TAILQ_NEXT(nb, list);
+ TAILQ_REMOVE(&peer->write_queue, nb, list);
+
+ free(nb->data);
+ free(nb);
+ }
+
+ free(peer->recv_buf->data);
+}
+
+void
+ktunnel_netbuf_create(struct netbuf **out, struct netbuf_list *head,
+ u_int8_t *data, u_int32_t length)
+{
+ struct netbuf *nb;
+
+ if ((nb = malloc(sizeof(struct netbuf))) == NULL)
+ fatal("malloc(): %s", errno_s);
+
+ nb->offset = 0;
+ nb->length = length;
+
+ if ((nb->data = malloc(nb->length)) == NULL)
+ fatal("malloc(): %s", errno_s);
+
+ if (data != NULL)
+ memcpy(nb->data, data, nb->length);
+
+ if (head != NULL)
+ TAILQ_INSERT_TAIL(head, nb, list);
+
+ if (out != NULL)
+ *out = nb;
+}
+
+void
+ktunnel_event_schedule(int fd, int type, int flags, void *udata)
+{
+ if (nchanges >= KQUEUE_EVENT_COUNT)
+ fatal("nchanges > KQUEUE_EVENT_COUNT");
+
+ EV_SET(&changelist[nchanges], fd, type, flags, 0, 0, udata);
+ nchanges++;
+}
+
+int
+ktunnel_read_local(struct peer *in, struct peer *out)
+{
+ int r;
+
+ printf("ktunnel_read_local: %p\n", in);
+
+ r = read(in->fd, in->recv_buf->data, in->recv_buf->length);
+ if (r == -1) {
+ if (errno != EINTR && errno != EAGAIN) {
+ printf("read error on local peer: %s\n", errno_s);
+ return (-1);
+ }
+
+ return (0);
+ }
+
+ if (r == 0) {
+ printf("local peer closed connection\n");
+ return (-1);
+ }
+
+ printf("ktunnel_read_local: %p -- %d bytes --> %p\n", in, r, out);
+
+ ktunnel_netbuf_create(NULL, &(out->write_queue), in->recv_buf->data, r);
+ return (ktunnel_write_remote(out));
+}
+
+int
+ktunnel_write_local(struct peer *peer)
+{
+ int r;
+ struct netbuf *nb;
+
+ while (!TAILQ_EMPTY(&peer->write_queue)) {
+ nb = TAILQ_FIRST(&peer->write_queue);
+
+ printf("ktunnel_write_local: %p writing %d/%d\n", peer,
+ nb->offset, nb->length);
+
+ r = write(peer->fd, (nb->data + nb->offset),
+ (nb->length - nb->offset));
+ if (r == -1) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+ peer->flags &= ~PEER_CAN_WRITE;
+ return (0);
+ default:
+ printf("failed to write to local peer: %s\n",
+ errno_s);
+ return (-1);
+ }
+ }
+
+ nb->offset += r;
+ printf("ktunnel_write_local: %p progress %d/%d\n", peer,
+ nb->offset, nb->length);
+
+ if (nb->offset == nb->length) {
+ TAILQ_REMOVE(&peer->write_queue, nb, list);
+ free(nb->data);
+ free(nb);
+ }
+ }
+
+ return (0);
+}
+
+int
+ktunnel_read_remote(struct peer *in, struct peer *out)
+{
+ int r;
+
+ r = SSL_read(in->ssl, in->recv_buf->data, in->recv_buf->length);
+ if (r <= 0) {
+ r = SSL_get_error(in->ssl, r);
+ switch (r) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ in->flags &= ~PEER_CAN_READ;
+ return (0);
+ default:
+ printf("failed to read from remote peer: %d, %s\n",
+ r, ssl_errno_s);
+ return (-1);
+ }
+ }
+
+ ktunnel_netbuf_create(NULL, &(out->write_queue), in->recv_buf->data, r);
+ return (ktunnel_write_local(out));
+}
+
+int
+ktunnel_write_remote(struct peer *peer)
+{
+ int r;
+ struct netbuf *nb;
+
+ while (!TAILQ_EMPTY(&peer->write_queue)) {
+ nb = TAILQ_FIRST(&peer->write_queue);
+
+ printf("ktunnel_write_remote: %p writing %d/%d bytes\n", peer,
+ nb->offset, nb->length);
+
+ r = SSL_write(peer->ssl, (nb->data + nb->offset),
+ (nb->length - nb->offset));
+ if (r <= 0) {
+ r = SSL_get_error(peer->ssl, r);
+ switch (r) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ peer->flags &= ~PEER_CAN_WRITE;
+ return (0);
+ default:
+ printf("failed to write to remote peer: %s\n",
+ ssl_errno_s);
+ return (-1);
+ }
+ }
+
+ nb->offset += r;
+ printf("ktunnel_write_remote: %p progress %d/%d\n", peer,
+ nb->offset, nb->length);
+
+ if (nb->offset == nb->length) {
+ TAILQ_REMOVE(&peer->write_queue, nb, list);
+ free(nb->data);
+ free(nb);
+ }
+ }
+
+ return (0);
+}
+
+void
+ktunnel_set_nonblock(int fd)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
+ fatal("fcntl(): get %s", errno_s);
+
+ flags |= O_NONBLOCK;
+ if (fcntl(fd, F_SETFL, flags) == -1)
+ fatal("fnctl(): set %s", errno_s);
+}
+
+void
+fatal(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+
+ printf("\n");
+
+ exit(1);
+}
diff --git a/examples/ktunnel/conf/ktunnel.conf b/examples/ktunnel/conf/ktunnel.conf
@@ -0,0 +1,20 @@
+# kTunnel configuration
+
+bind 127.0.0.1 8888
+load ./ktunnel.so
+
+# Regexes here are incorrect.
+validator v_host regex ^.*$
+validator v_port regex ^[0-9]*$
+
+domain 127.0.0.1 {
+ certfile cert/server.crt
+ certkey cert/server.key
+
+ static /connect open_connection
+
+ params get /connect {
+ validate host v_host
+ validate port v_port
+ }
+}
diff --git a/examples/ktunnel/src/ktunnel.c b/examples/ktunnel/src/ktunnel.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2014 Joris Vink <joris@coders.se>
+ *
+ * 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 <kore/kore.h>
+#include <kore/http.h>
+
+#include <limits.h>
+
+/*
+ * KTunnel shows how Kore exposes its net internals to its libraries
+ * and how we can "abuse" these internals to create a "anything"
+ * over HTTPS tunnel.
+ */
+
+int open_connection(struct http_request *);
+
+static int ktunnel_pipe_data(struct netbuf *);
+static void ktunnel_pipe_disconnect(struct connection *);
+static int ktunnel_pipe_create(struct connection *,
+ const char *, const char *);
+
+/*
+ * Receive a request to open a new connection.
+ */
+int
+open_connection(struct http_request *req)
+{
+ char *host, *port;
+
+ /* Don't want to deal with SPDY connections. */
+ if (req->owner->proto != CONN_PROTO_HTTP) {
+ http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
+ return (KORE_RESULT_OK);
+ }
+
+ /* Parse the query string and grab our arguments. */
+ http_populate_arguments(req);
+ if (!http_argument_get_string("host", &host, NULL) ||
+ !http_argument_get_string("port", &port, NULL)) {
+ http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
+ return (KORE_RESULT_OK);
+ }
+
+ /* Create our tunnel. */
+ if (!ktunnel_pipe_create(req->owner, host, port)) {
+ http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
+ return (KORE_RESULT_OK);
+ }
+
+ /*
+ * Hack so http_response() doesn't end up queueing a new
+ * netbuf for receiving more HTTP requests on the same connection.
+ */
+ req->owner->flags |= CONN_CLOSE_EMPTY;
+
+ /* Respond to the client now that we're good to go. */
+ http_response(req, HTTP_STATUS_OK, NULL, 0);
+
+ /* Unset this so we don't disconnect after returning. */
+ req->owner->flags &= ~CONN_CLOSE_EMPTY;
+
+ return (KORE_RESULT_OK);
+}
+
+/*
+ * Connect to our target host:port and attach it to a struct connection that
+ * Kore understands. We set the disconnect method so we get a callback
+ * whenever either of the connections will go away so we can cleanup the
+ * one it is attached to.
+ *
+ * We are storing the "piped" connection in hdlr_extra.
+ */
+static int
+ktunnel_pipe_create(struct connection *c, const char *host, const char *port)
+{
+ struct sockaddr_in sin;
+ struct connection *pipe;
+ u_int16_t nport;
+ int fd, err;
+ struct netbuf *nb, *next;
+
+ nport = kore_strtonum(port, 10, 1, SHRT_MAX, &err);
+ if (err == KORE_RESULT_ERROR) {
+ kore_log(LOG_ERR, "invalid port given %s", port);
+ return (KORE_RESULT_ERROR);
+ }
+
+ if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+ kore_log(LOG_ERR, "socket(): %s", errno_s);
+ return (KORE_RESULT_ERROR);
+ }
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(nport);
+ sin.sin_addr.s_addr = inet_addr(host);
+
+ kore_log(LOG_NOTICE, "Attempting to connect to %s:%s", host, port);
+
+ if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
+ close(fd);
+ kore_log(LOG_ERR, "connect(): %s", errno_s);
+ return (KORE_RESULT_ERROR);
+ }
+
+ if (!kore_connection_nonblock(fd)) {
+ close(fd);
+ return (KORE_RESULT_ERROR);
+ }
+
+ pipe = kore_connection_new(c);
+ pipe->fd = fd;
+ pipe->addr.ipv4 = sin;
+ pipe->read = net_read;
+ pipe->write = net_write;
+ pipe->addrtype = AF_INET;
+ pipe->proto = CONN_PROTO_UNKNOWN;
+ pipe->state = CONN_STATE_ESTABLISHED;
+
+ c->hdlr_extra = pipe;
+ pipe->hdlr_extra = c;
+ c->disconnect = ktunnel_pipe_disconnect;
+ pipe->disconnect = ktunnel_pipe_disconnect;
+
+ kore_worker_connection_add(pipe);
+ kore_connection_start_idletimer(pipe);
+
+ for (nb = TAILQ_FIRST(&(c->recv_queue)); nb != NULL; nb = next) {
+ next = TAILQ_NEXT(nb, list);
+ TAILQ_REMOVE(&(c->recv_queue), nb, list);
+ kore_mem_free(nb->buf);
+ kore_pool_put(&nb_pool, nb);
+ }
+
+ kore_platform_event_all(pipe->fd, pipe);
+
+ net_recv_queue(c, NETBUF_SEND_PAYLOAD_MAX,
+ NETBUF_CALL_CB_ALWAYS, NULL, ktunnel_pipe_data);
+ net_recv_queue(pipe, NETBUF_SEND_PAYLOAD_MAX,
+ NETBUF_CALL_CB_ALWAYS, NULL, ktunnel_pipe_data);
+
+ printf("connection started to %s (%p -> %p)\n", host, c, pipe);
+ return (KORE_RESULT_OK);
+}
+
+/*
+ * Called everytime new data is read from any of the connections
+ * that are part of a pipe.
+ */
+static int
+ktunnel_pipe_data(struct netbuf *nb)
+{
+ struct connection *src = nb->owner;
+ struct connection *dst = src->hdlr_extra;
+
+ printf("received %d bytes on pipe %p (-> %p)\n", nb->s_off, src, dst);
+
+ net_send_queue(dst, nb->buf, nb->s_off, NULL, NETBUF_LAST_CHAIN);
+ net_send_flush(dst);
+
+ /* Reuse the netbuf so we don't have to recreate them all the time. */
+ nb->s_off = 0;
+
+ return (KORE_RESULT_OK);
+}
+
+/*
+ * Called when either part of the pipe disconnects.
+ */
+static void
+ktunnel_pipe_disconnect(struct connection *c)
+{
+ struct connection *pipe = c->hdlr_extra;
+
+ printf("ktunnel_pipe_disconnect(%p)->%p\n", c, pipe);
+
+ if (pipe != NULL) {
+ /* Prevent Kore from calling kore_mem_free() on hdlr_extra. */
+ c->hdlr_extra = NULL;
+ kore_connection_disconnect(pipe);
+ }
+}