Browse files

Proxy-PROXY support

A mode that enables transparently passing the PROXY header generated by
HAProxy et. al. through to the backend connection. This can be useful
if you are using a TCP load balancer (such as HAProxy in tcp mode) to
distribute encrypted streams to stud before they are unwrapped.
  • Loading branch information...
1 parent f99e976 commit d78fd49e701caea253d0421d5f2ee3877aba60c2 @zenazn zenazn committed Jun 20, 2012
Showing with 88 additions and 6 deletions.
  1. +8 −3 README.md
  2. +25 −2 configuration.c
  3. +1 −0 configuration.h
  4. +54 −1 stud.c
View
11 README.md
@@ -19,9 +19,11 @@ maxmium connection behavior, availability of service, etc.
`stud` will optionally write the client IP address as the first few octets
(depending on IPv4 or IPv6) to the backend--or provide that information
-using HAProxy's PROXY protocol. In this way, backends who care about the
-client IP can still access it even though `stud` itself appears to be the
-connected client.
+using HAProxy's PROXY protocol. When used with the PROXY protocol, `stud` can
+also transparently pass an existing PROXY header to the cleartext stream. This
+is especially useful if a TCP proxy is used in front of `stud`. Using either of
+these techniques, backends who care about the client IP can still access it even
+though `stud` itself appears to be the connected client.
Thanks to a contribution from Emeric at Exceliance (the folks behind HAProxy),
a special build of `stud` can be made that utilitizes shared memory to
@@ -129,6 +131,9 @@ Detail about the entire set of options can be found by invoking `stud -h`:
--write-proxy Write HaProxy's PROXY (IPv4 or IPv6) protocol line
before actual data
(Default: off)
+ --proxy-proxy Proxy HaProxy's PROXY (IPv4 or IPv6) protocol line
+ before actual data
+ (Default: off)
-t --test Test configuration and exit
-V --version Print program version and exit
View
27 configuration.c
@@ -46,6 +46,7 @@
#define CFG_WRITE_IP "write-ip"
#define CFG_WRITE_PROXY "write-proxy"
#define CFG_PEM_FILE "pem-file"
+#define CFG_PROXY_PROXY "proxy-proxy"
#ifdef USE_SHARED_CACHE
#define CFG_SHARED_CACHE "shared-cache"
@@ -114,6 +115,7 @@ stud_config * config_new (void) {
r->PMODE = SSL_SERVER;
r->WRITE_IP_OCTET = 0;
r->WRITE_PROXY_LINE = 0;
+ r->PROXY_PROXY_LINE = 0;
r->CHROOT = NULL;
r->UID = 0;
r->GID = 0;
@@ -675,6 +677,9 @@ void config_param_validate (char *k, char *v, stud_config *cfg, char *file, int
else if (strcmp(k, CFG_WRITE_PROXY) == 0) {
r = config_param_val_bool(v, &cfg->WRITE_PROXY_LINE);
}
+ else if (strcmp(k, CFG_PROXY_PROXY) == 0) {
+ r = config_param_val_bool(v, &cfg->PROXY_PROXY_LINE);
+ }
else if (strcmp(k, CFG_PEM_FILE) == 0) {
if (v != NULL && strlen(v) > 0) {
if (stat(v, &st) != 0) {
@@ -906,6 +911,9 @@ void config_print_usage_fd (char *prog, stud_config *cfg, FILE *out) {
fprintf(out, " --write-proxy Write HaProxy's PROXY (IPv4 or IPv6) protocol line\n" );
fprintf(out, " before actual data\n");
fprintf(out, " (Default: %s)\n", config_disp_bool(cfg->WRITE_PROXY_LINE));
+ fprintf(out, " --proxy-proxy Proxy HaProxy's PROXY (IPv4 or IPv6) protocol line\n" );
+ fprintf(out, " before actual data\n");
+ fprintf(out, " (Default: %s)\n", config_disp_bool(cfg->PROXY_PROXY_LINE));
fprintf(out, "\n");
fprintf(out, " -t --test Test configuration and exit\n");
fprintf(out, " -V --version Print program version and exit\n");
@@ -1072,7 +1080,7 @@ void config_print_default (FILE *fd, stud_config *cfg) {
fprintf(fd, "# Report client address by writing IP before sending data\n");
fprintf(fd, "#\n");
- fprintf(fd, "# NOTE: This option is mutually exclusive with option %s.\n", CFG_WRITE_PROXY);
+ fprintf(fd, "# NOTE: This option is mutually exclusive with option %s and %s.\n", CFG_WRITE_PROXY, CFG_PROXY_PROXY);
fprintf(fd, "#\n");
fprintf(fd, "# type: boolean\n");
fprintf(fd, FMT_STR, CFG_WRITE_IP, config_disp_bool(cfg->WRITE_IP_OCTET));
@@ -1082,12 +1090,20 @@ void config_print_default (FILE *fd, stud_config *cfg) {
fprintf(fd, "# http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt\n");
fprintf(fd, "# for details.\n");
fprintf(fd, "#\n");
- fprintf(fd, "# NOTE: This option is mutually exclusive with option %s.\n", CFG_WRITE_IP);
+ fprintf(fd, "# NOTE: This option is mutually exclusive with option %s and %s.\n", CFG_WRITE_IP, CFG_PROXY_PROXY);
fprintf(fd, "#\n");
fprintf(fd, "# type: boolean\n");
fprintf(fd, FMT_STR, CFG_WRITE_PROXY, config_disp_bool(cfg->WRITE_PROXY_LINE));
fprintf(fd, "\n");
+ fprintf(fd, "# Proxy an existing SENDPROXY protocol header through this request.\n");
+ fprintf(fd, "#\n");
+ fprintf(fd, "# NOTE: This option is mutually exclusive with option %s and %s.\n", CFG_WRITE_IP, CFG_WRITE_PROXY);
+ fprintf(fd, "#\n");
+ fprintf(fd, "# type: boolean\n");
+ fprintf(fd, FMT_STR, CFG_PROXY_PROXY, config_disp_bool(cfg->PROXY_PROXY_LINE));
+ fprintf(fd, "\n");
+
fprintf(fd, "# EOF\n");
}
#endif /* NO_CONFIG_FILE */
@@ -1134,6 +1150,7 @@ void config_parse_cli(int argc, char **argv, stud_config *cfg) {
{ CFG_DAEMON, 0, &cfg->DAEMONIZE, 1 },
{ CFG_WRITE_IP, 0, &cfg->WRITE_IP_OCTET, 1 },
{ CFG_WRITE_PROXY, 0, &cfg->WRITE_PROXY_LINE, 1 },
+ { CFG_PROXY_PROXY, 0, &cfg->PROXY_PROXY_LINE, 1 },
{ "test", 0, NULL, 't' },
{ "version", 0, NULL, 'V' },
@@ -1256,6 +1273,12 @@ void config_parse_cli(int argc, char **argv, stud_config *cfg) {
if (cfg->WRITE_IP_OCTET && cfg->WRITE_PROXY_LINE)
config_die("Options --write-ip and --write-proxy are mutually exclusive.");
+ if (cfg->WRITE_PROXY_LINE && cfg->PROXY_PROXY_LINE)
+ config_die("Options --write-proxy and --proxy-proxy are mutually exclusive.");
+
+ if (cfg->WRITE_IP_OCTET && cfg->PROXY_PROXY_LINE)
+ config_die("Options --write-ip and --proxy-proxy are mutually exclusive.");
+
if (cfg->DAEMONIZE) {
cfg->SYSLOG = 1;
cfg->QUIET = 1;
View
1 configuration.h
@@ -37,6 +37,7 @@ struct __stud_config {
PROXY_MODE PMODE;
int WRITE_IP_OCTET;
int WRITE_PROXY_LINE;
+ int PROXY_PROXY_LINE;
char *CHROOT;
uid_t UID;
gid_t GID;
View
55 stud.c
@@ -57,6 +57,7 @@
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/engine.h>
+#include <openssl/bio.h>
#include <ev.h>
#include "ringbuffer.h"
@@ -132,6 +133,8 @@ typedef struct proxystate {
ev_io ev_r_clear; /* Clear stream write event */
ev_io ev_w_clear; /* Clear stream read event */
+ ev_io ev_proxy; /* proxy read event */
+
int fd_up; /* Upstream (client) socket */
int fd_down; /* Downstream (backend) socket */
@@ -738,6 +741,7 @@ static void shutdown_proxy(proxystate *ps, SHUTDOWN_REQUESTOR req) {
ev_io_stop(loop, &ps->ev_w_connect);
ev_io_stop(loop, &ps->ev_w_clear);
ev_io_stop(loop, &ps->ev_r_clear);
+ ev_io_stop(loop, &ps->ev_proxy);
close(ps->fd_up);
close(ps->fd_down);
@@ -982,6 +986,47 @@ static void end_handshake(proxystate *ps) {
ev_io_start(loop, &ps->ev_w_ssl);
}
+static void client_proxy_proxy(struct ev_loop *loop, ev_io *w, int revents) {
+ (void) revents;
+ int t;
+ char *proxy = tcp_proxy_line, *end = tcp_proxy_line + sizeof(tcp_proxy_line);
+ proxystate *ps = (proxystate *)w->data;
+ BIO *b = SSL_get_rbio(ps->ssl);
+
+ // Copy characters one-by-one until we hit a \n or an error
+ while (proxy != end && (t = BIO_read(b, proxy, 1)) == 1) {
+ if (*proxy++ == '\n') break;
+ }
+
+ if (proxy == end) {
+ LOG("{client} Unexpectedly long PROXY line. Perhaps a malformed request?");
+ shutdown_proxy(ps, SHUTDOWN_SSL);
+ }
+ else if (t == 1) {
+ if (ringbuffer_is_full(&ps->ring_ssl2clear)) {
+ LOG("{client} Error writing PROXY line");
+ shutdown_proxy(ps, SHUTDOWN_SSL);
+ return;
+ }
+
+ char *ring = ringbuffer_write_ptr(&ps->ring_ssl2clear);
+ memcpy(ring, tcp_proxy_line, proxy - tcp_proxy_line);
+ ringbuffer_write_append(&ps->ring_ssl2clear, proxy - tcp_proxy_line);
+
+ // Finished reading the PROXY header
+ if (*(proxy - 1) == '\n') {
+ ev_io_stop(loop, &ps->ev_proxy);
+
+ // Start the real handshake
+ start_handshake(ps, SSL_ERROR_WANT_READ);
+ }
+ }
+ else if (!BIO_should_retry(b)) {
+ LOG("{client} Unexpected error reading PROXY line");
+ shutdown_proxy(ps, SHUTDOWN_SSL);
+ }
+}
+
/* The libev I/O handler during the OpenSSL handshake phase. Basically, just
* let OpenSSL do what it likes with the socket and obey its requests for reads
* or writes */
@@ -1184,6 +1229,8 @@ static void handle_accept(struct ev_loop *loop, ev_io *w, int revents) {
ev_io_init(&ps->ev_r_handshake, client_handshake, client, EV_READ);
ev_io_init(&ps->ev_w_handshake, client_handshake, client, EV_WRITE);
+ ev_io_init(&ps->ev_proxy, client_proxy_proxy, client, EV_READ);
+
ev_io_init(&ps->ev_w_connect, handle_connect, back, EV_WRITE);
ev_io_init(&ps->ev_w_clear, clear_write, back, EV_WRITE);
@@ -1193,14 +1240,20 @@ static void handle_accept(struct ev_loop *loop, ev_io *w, int revents) {
ps->ev_w_ssl.data = ps;
ps->ev_r_clear.data = ps;
ps->ev_w_clear.data = ps;
+ ps->ev_proxy.data = ps;
ps->ev_w_connect.data = ps;
ps->ev_r_handshake.data = ps;
ps->ev_w_handshake.data = ps;
/* Link back proxystate to SSL state */
SSL_set_app_data(ssl, ps);
- start_handshake(ps, SSL_ERROR_WANT_READ); /* for client-first handshake */
+ if (CONFIG->PROXY_PROXY_LINE) {
+ ev_io_start(loop, &ps->ev_proxy);
+ }
+ else {
+ start_handshake(ps, SSL_ERROR_WANT_READ); /* for client-first handshake */
+ }
}

0 comments on commit d78fd49

Please sign in to comment.