kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

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:
examples/ktunnel/.gitignore | 5+++++
examples/ktunnel/README.md | 35+++++++++++++++++++++++++++++++++++
examples/ktunnel/client/Makefile | 16++++++++++++++++
examples/ktunnel/client/client.c | 656+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
examples/ktunnel/conf/ktunnel.conf | 20++++++++++++++++++++
examples/ktunnel/src/ktunnel.c | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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); + } +}