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 c3401fe3487b7cb4d6f9ced4112c2180ca8cbe42
parent d3332d59214ec950d62af9f7312883321ae12a76
Author: Joris Vink <joris@coders.se>
Date:   Wed,  9 Dec 2015 21:29:53 +0100

Make the TLS proxy example much better.

Now allows multiple backends based on SNI that was
set during TLS handshake.

The connection phase for the backends is now fully
non blocking.

Diffstat:
examples/tls-proxy/README.md | 5+++--
examples/tls-proxy/conf/tls-proxy.conf | 16+++++++---------
examples/tls-proxy/src/proxy.c | 257++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
3 files changed, 201 insertions(+), 77 deletions(-)

diff --git a/examples/tls-proxy/README.md b/examples/tls-proxy/README.md @@ -1,8 +1,9 @@ Kore as a TLS-proxy. -Note that this example requires a Kore binary built with NOHTTP set to 1. +Edit src/proxy.c and add your backends to the backends[] data structure. -Edit src/proxy.c and update PROXY_HOST and PROXY_PORT to match your needs. +If you want to reduce attack surface you can build Kore with NOHTTP=1 to +completely remove the HTTP component and only run the net code. Run: ``` diff --git a/examples/tls-proxy/conf/tls-proxy.conf b/examples/tls-proxy/conf/tls-proxy.conf @@ -1,19 +1,17 @@ # Kore as a TLS proxy configuration. -# -# Be sure to update the host and port to proxy -# towards in src/proxy.c and rebuild. -# -# I recommend using Kore built with NOHTTP=1 if -# you want to use this. load ./tls-proxy.so tls_dhparam dh2048.pem -# Keep the proxy_setup callback. -bind 127.0.0.1 8888 proxy_setup +# +# Bind the proxy to a given IP and port. For every +# connection we receive we will call client_setup +# so it can kick things in action. +# +bind 127.0.0.1 8888 client_setup # Setup domain for TLS usage. -domain 127.0.0.1 { +domain localhost { certfile cert/server.crt certkey cert/server.key } diff --git a/examples/tls-proxy/src/proxy.c b/examples/tls-proxy/src/proxy.c @@ -20,125 +20,250 @@ #include <kore/kore.h> /* - * In this example Kore acts as a simple TLS proxy. - * Be sure to update PROXY_HOST and PROXY_PORT to reflect - * your endpoint. + * In this example Kore acts as a TLS proxy shuffling data between + * an encrypted connection and a plain text backend. * - * Note - right now the connect() call in proxy_setup() is still - * done synchronously, might change in the future in this example. + * It will look at the TLS SNI extension to figure out what backend + * to use for the connection when it comes in. * - * Hint: enabling client certificates in Kore still works with this :-) + * Add your backends to the data structure below. */ -#define PROXY_HOST "127.0.0.1" -#define PROXY_PORT 80 +/* Default timeouts, 5 seconds for connecting, 15 seconds otherwise. */ +#define PROXY_TIMEOUT (15 * 1000) +#define PROXY_CONNECT_TIMEOUT (5 * 1000) -void proxy_setup(struct connection *); -void proxy_disconnect(struct connection *); -int proxy_data(struct netbuf *); -int proxy_handle(struct connection *); +/* All domains and their backends. */ +struct { + const char *name; + const char *ip; + const u_int16_t port; +} backends[] = { + { "localhost", "127.0.0.1", 8080 }, + { NULL, NULL, 0 } +}; +int client_handle(struct connection *); +void client_setup(struct connection *); + +void disconnect(struct connection *); +int pipe_data(struct netbuf *); + +int backend_handle_connect(struct connection *); +int backend_handle_default(struct connection *); + +/* + * Called for every new connection on a certain ip/port. Which one is + * configured in the TLS proxy its configuration file. + */ void -proxy_setup(struct connection *c) +client_setup(struct connection *c) { - int fd; - struct sockaddr_in sin; - struct connection *proxy; + int i, fd; + struct connection *backend; - if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { - kore_log(LOG_ERR, "socket(): %s", errno_s); + /* Paranoia. */ + if (c->ssl->session == NULL || + c->ssl->session->tlsext_hostname == NULL) { kore_connection_disconnect(c); return; } - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_port = htons(PROXY_PORT); - sin.sin_addr.s_addr = inet_addr(PROXY_HOST); + /* Figure out what backend to use. */ + for (i = 0; backends[i].name != NULL; i++) { + if (!strcasecmp(backends[i].name, + c->ssl->session->tlsext_hostname)) + break; + } - /* Blocking connect(), perhaps we can improve on that later. */ - if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { - kore_log(LOG_ERR, "connect(): %s", errno_s); - close(fd); + /* If we don't have any backends, we just disconnect the client. */ + if (backends[i].name == NULL) { + kore_connection_disconnect(c); + return; + } + + /* Create new socket for the backend connection. */ + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + kore_log(LOG_ERR, "socket(): %s", errno_s); kore_connection_disconnect(c); return; } + /* Set it to non blocking as well. */ if (!kore_connection_nonblock(fd, 1)) { close(fd); kore_connection_disconnect(c); return; } - proxy = kore_connection_new(NULL); + /* Grab a new connection from Kore to hook backend into. */ + backend = kore_connection_new(NULL); - proxy->fd = fd; - proxy->addr.ipv4 = sin; - proxy->read = net_read; - proxy->write = net_write; - proxy->addrtype = AF_INET; - proxy->proto = CONN_PROTO_UNKNOWN; - proxy->state = CONN_STATE_ESTABLISHED; + /* Prepare our connection. */ + backend->addrtype = AF_INET; + backend->addr.ipv4.sin_family = AF_INET; + backend->addr.ipv4.sin_port = htons(backends[i].port); + backend->addr.ipv4.sin_addr.s_addr = inet_addr(backends[i].ip); - proxy->idle_timer.length = 60000; - c->idle_timer.length = 60000; + /* Set the file descriptor for the backend. */ + backend->fd = fd; - c->hdlr_extra = proxy; - proxy->hdlr_extra = c; + /* Default write/read callbacks for backend. */ + backend->read = net_read; + backend->write = net_write; - c->handle = proxy_handle; - c->disconnect = proxy_disconnect; - proxy->handle = proxy_handle; - proxy->disconnect = proxy_disconnect; + /* Connection type (unknown to Kore). */ + backend->proto = CONN_PROTO_UNKNOWN; + backend->state = CONN_STATE_ESTABLISHED; - kore_connection_start_idletimer(proxy); - kore_platform_event_all(proxy->fd, proxy); + /* The backend idle timer is set first to connection timeout. */ + backend->idle_timer.length = PROXY_CONNECT_TIMEOUT; - net_recv_queue(c, NETBUF_SEND_PAYLOAD_MAX, - NETBUF_CALL_CB_ALWAYS, proxy_data); - net_recv_queue(proxy, NETBUF_SEND_PAYLOAD_MAX, - NETBUF_CALL_CB_ALWAYS, proxy_data); + /* The client idle timer is set to default idle time. */ + c->idle_timer.length = PROXY_TIMEOUT; + + /* Now link both the client and the backend connection together. */ + c->hdlr_extra = backend; + backend->hdlr_extra = c; + + /* + * The handle function pointer for the backend is set to the + * backend_handle_connect() while connecting. + */ + c->handle = client_handle; + backend->handle = backend_handle_connect; - kore_log(LOG_NOTICE, "new connection alright, us:%p proxy:%p", c, proxy); + /* Set the disconnect method for both connections. */ + c->disconnect = disconnect; + backend->disconnect = disconnect; - /* We must set the state for this connection ourselves. */ + /* Queue write events for the backend connection for now. */ + kore_platform_schedule_write(backend->fd, backend); + + /* Start idle timer for the backend. */ + kore_connection_start_idletimer(backend); + + /* Set our client connection to established. */ c->state = CONN_STATE_ESTABLISHED; - TAILQ_INSERT_TAIL(&connections, proxy, list); + + /* Insert the backend into the list of Kore connections. */ + TAILQ_INSERT_TAIL(&connections, backend, list); + + /* Kick off connecting. */ + backend->flags |= CONN_WRITE_POSSIBLE; + backend->handle(backend); } +/* + * This function is called for backends while they are connecting. + * In here we check for write events and attempt to connect() to the + * backend. + * + * Once a connection is established we set the backend handle function + * pointer to the backend_handle_default() callback and setup the reads + * for both the backend and the client connection we received. + */ int -proxy_handle(struct connection *c) +backend_handle_connect(struct connection *c) +{ + int ret; + + /* We will get a write notification when we can progress. */ + if (!(c->flags & CONN_WRITE_POSSIBLE)) + return (KORE_RESULT_OK); + + kore_connection_stop_idletimer(c); + + /* Attempt connecting. */ + ret = connect(c->fd, (struct sockaddr *)&c->addr.ipv4, + sizeof(c->addr.ipv4)); + + /* If we failed check why, we are non blocking. */ + if (ret == -1) { + /* If we got a real error, disconnect. */ + if (errno != EALREADY && errno != EINPROGRESS && + errno != EISCONN) { + kore_log(LOG_ERR, "connect(): %s", errno_s); + return (KORE_RESULT_ERROR); + } + + /* Clean the write flag, we'll be called later. */ + if (errno != EISCONN) { + c->flags &= ~CONN_WRITE_POSSIBLE; + kore_connection_start_idletimer(c); + return (KORE_RESULT_OK); + } + } + + /* The connection to the backend succeeded. */ + c->handle = backend_handle_default; + + /* Setup read calls for both backend and its client. */ + net_recv_queue(c, NETBUF_SEND_PAYLOAD_MAX, + NETBUF_CALL_CB_ALWAYS, pipe_data); + net_recv_queue(c->hdlr_extra, NETBUF_SEND_PAYLOAD_MAX, + NETBUF_CALL_CB_ALWAYS, pipe_data); + + /* Allow for all events now. */ + kore_connection_start_idletimer(c); + kore_platform_event_all(c->fd, c); + + return (KORE_RESULT_OK); +} + +/* + * Called for connection activity on a backend, just forwards + * to the default Kore connection handling for now. + */ +int +backend_handle_default(struct connection *c) { - kore_log(LOG_NOTICE, "connection activity on %p", c); return (kore_connection_handle(c)); } -void -proxy_disconnect(struct connection *c) +/* + * Called for connection activity on a client, just forwards + * to the default Kore connection handling for now. + */ +int +client_handle(struct connection *c) { - struct connection *proxy = (struct connection *)c->hdlr_extra; + return (kore_connection_handle(c)); +} - kore_log(LOG_NOTICE, "disconnecting %p (proxy: %p)", c, proxy); +/* + * Called whenever a client or its backend have disconnected. + * This will disconnect the matching paired connection as well. + */ +void +disconnect(struct connection *c) +{ + struct connection *pair = c->hdlr_extra; c->hdlr_extra = NULL; - if (proxy != NULL) { - proxy->hdlr_extra = NULL; - kore_connection_disconnect(proxy); + if (pair != NULL) { + pair->hdlr_extra = NULL; + kore_connection_disconnect(pair); } } +/* + * Called whenever data is available that must be piped through + * to the paired connection. (client<>backend or backend<>client). + */ int -proxy_data(struct netbuf *nb) +pipe_data(struct netbuf *nb) { struct connection *src = nb->owner; - struct connection *proxy = src->hdlr_extra; + struct connection *dst = src->hdlr_extra; - kore_log(LOG_NOTICE, "proxying %u bytes", nb->s_off); + /* Flush data out towards destination. */ + net_send_queue(dst, nb->buf, nb->s_off); + net_send_flush(dst); - net_send_queue(proxy, nb->buf, nb->s_off); - net_send_flush(proxy); - net_recv_reset(src, NETBUF_SEND_PAYLOAD_MAX, proxy_data); + /* Reset read for source. */ + net_recv_reset(src, NETBUF_SEND_PAYLOAD_MAX, pipe_data); return (KORE_RESULT_OK); }