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.
This commit is contained in:
Joris Vink 2015-12-09 21:29:53 +01:00
parent d3332d5921
commit c3401fe348
3 changed files with 212 additions and 88 deletions

View File

@ -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:
```

View File

@ -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
}

View File

@ -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;
/* Paranoia. */
if (c->ssl->session == NULL ||
c->ssl->session->tlsext_hostname == NULL) {
kore_connection_disconnect(c);
return;
}
/* 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;
}
/* 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;
}
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);
/* 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);
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;
kore_log(LOG_NOTICE, "new connection alright, us:%p proxy:%p", c, proxy);
/* Now link both the client and the backend connection together. */
c->hdlr_extra = backend;
backend->hdlr_extra = c;
/* We must set the state for this connection ourselves. */
/*
* 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;
/* Set the disconnect method for both connections. */
c->disconnect = disconnect;
backend->disconnect = disconnect;
/* 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)
{
kore_log(LOG_NOTICE, "connection activity on %p", c);
return (kore_connection_handle(c));
}
int ret;
void
proxy_disconnect(struct connection *c)
{
struct connection *proxy = (struct connection *)c->hdlr_extra;
/* We will get a write notification when we can progress. */
if (!(c->flags & CONN_WRITE_POSSIBLE))
return (KORE_RESULT_OK);
kore_log(LOG_NOTICE, "disconnecting %p (proxy: %p)", c, proxy);
kore_connection_stop_idletimer(c);
c->hdlr_extra = NULL;
/* Attempt connecting. */
ret = connect(c->fd, (struct sockaddr *)&c->addr.ipv4,
sizeof(c->addr.ipv4));
if (proxy != NULL) {
proxy->hdlr_extra = NULL;
kore_connection_disconnect(proxy);
/* 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);
}
}
}
int
proxy_data(struct netbuf *nb)
{
struct connection *src = nb->owner;
struct connection *proxy = src->hdlr_extra;
/* The connection to the backend succeeded. */
c->handle = backend_handle_default;
kore_log(LOG_NOTICE, "proxying %u bytes", nb->s_off);
/* 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);
net_send_queue(proxy, nb->buf, nb->s_off);
net_send_flush(proxy);
net_recv_reset(src, NETBUF_SEND_PAYLOAD_MAX, proxy_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)
{
return (kore_connection_handle(c));
}
/*
* Called for connection activity on a client, just forwards
* to the default Kore connection handling for now.
*/
int
client_handle(struct connection *c)
{
return (kore_connection_handle(c));
}
/*
* 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 (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
pipe_data(struct netbuf *nb)
{
struct connection *src = nb->owner;
struct connection *dst = src->hdlr_extra;
/* Flush data out towards destination. */
net_send_queue(dst, nb->buf, nb->s_off);
net_send_flush(dst);
/* Reset read for source. */
net_recv_reset(src, NETBUF_SEND_PAYLOAD_MAX, pipe_data);
return (KORE_RESULT_OK);
}