dovecot-2.2: lib-master: Added support for HAProxy protocol.

dovecot at dovecot.org dovecot at dovecot.org
Tue Aug 18 18:02:19 UTC 2015


details:   http://hg.dovecot.org/dovecot-2.2/rev/4d7a83ddb644
changeset: 18952:4d7a83ddb644
user:      Timo Sirainen <tss at iki.fi>
date:      Tue Aug 18 20:39:06 2015 +0300
description:
lib-master: Added support for HAProxy protocol.
Patch by Stephan Bosch - with some small changes.

diffstat:

 src/lib-master/Makefile.am               |    1 +
 src/lib-master/master-service-haproxy.c  |  476 +++++++++++++++++++++++++++++++
 src/lib-master/master-service-private.h  |    9 +
 src/lib-master/master-service-settings.c |    8 +-
 src/lib-master/master-service-settings.h |    3 +
 src/lib-master/master-service.c          |    9 +-
 src/lib-master/service-settings.h        |    1 +
 src/master/master-settings.c             |    4 +-
 src/master/service-process.c             |    2 +
 9 files changed, 510 insertions(+), 3 deletions(-)

diffs (truncated from 643 to 300 lines):

diff -r 52368e60177c -r 4d7a83ddb644 src/lib-master/Makefile.am
--- a/src/lib-master/Makefile.am	Tue Aug 18 20:23:45 2015 +0300
+++ b/src/lib-master/Makefile.am	Tue Aug 18 20:39:06 2015 +0300
@@ -21,6 +21,7 @@
 	master-login.c \
 	master-login-auth.c \
 	master-service.c \
+	master-service-haproxy.c \
 	master-service-settings.c \
 	master-service-settings-cache.c \
 	master-service-ssl.c \
diff -r 52368e60177c -r 4d7a83ddb644 src/lib-master/master-service-haproxy.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-master/master-service-haproxy.c	Tue Aug 18 20:39:06 2015 +0300
@@ -0,0 +1,476 @@
+/* Copyright (c) 2013-2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "ioloop.h"
+#include "str-sanitize.h"
+#include "master-service-private.h"
+#include "master-service-settings.h"
+
+#define HAPROXY_V1_MAX_HEADER_SIZE (108)
+
+enum {
+	HAPROXY_CMD_LOCAL = 0x00,
+	HAPROXY_CMD_PROXY = 0x01
+};
+
+enum {
+	HAPROXY_AF_UNSPEC = 0x00,
+	HAPROXY_AF_INET   = 0x01,
+	HAPROXY_AF_INET6  = 0x02,
+	HAPROXY_AF_UNIX   = 0x03
+};
+
+enum {
+	HAPROXY_SOCK_UNSPEC = 0x00,
+	HAPROXY_SOCK_STREAM = 0x01,
+	HAPROXY_SOCK_DGRAM  = 0x02
+};
+
+static const char haproxy_v2sig[12] =
+	"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
+
+struct haproxy_header_v2 {
+	uint8_t sig[12];
+	uint8_t ver_cmd;
+	uint8_t fam;
+	uint16_t len;
+};
+
+struct haproxy_data_v2 {
+	union {
+		struct {  /* for TCP/UDP over IPv4, len = 12 */
+			uint32_t src_addr;
+			uint32_t dst_addr;
+			uint16_t src_port;
+			uint16_t dst_port;
+		} ip4;
+		struct {  /* for TCP/UDP over IPv6, len = 36 */
+			uint8_t  src_addr[16];
+			uint8_t  dst_addr[16];
+			uint16_t src_port;
+			uint16_t dst_port;
+		} ip6;
+		struct {  /* for AF_UNIX sockets, len = 216 */
+			uint8_t src_addr[108];
+			uint8_t dst_addr[108];
+		} unx;
+	} addr;
+};
+
+struct master_service_haproxy_conn {
+	struct master_service_connection conn;
+
+	struct master_service_haproxy_conn *prev, *next;
+	
+	struct master_service *service;
+
+	struct io *io;
+	struct timeout *to;
+};
+
+static void
+master_service_haproxy_conn_free(struct master_service_haproxy_conn *hpconn)
+{
+	struct master_service *service = hpconn->service;
+
+	DLLIST_REMOVE(&service->haproxy_conns, hpconn);
+
+	if (hpconn->io != NULL)
+		io_remove(&hpconn->io);
+	if (hpconn->to != NULL)
+		timeout_remove(&hpconn->to);
+	i_free(hpconn);
+}
+
+static void
+master_service_haproxy_conn_failure(struct master_service_haproxy_conn *hpconn)
+{
+	struct master_service *service = hpconn->service;
+	struct master_service_connection conn = hpconn->conn;
+
+	master_service_haproxy_conn_free(hpconn);
+	master_service_client_connection_handled(service, &conn);
+}
+
+static void
+master_service_haproxy_conn_success(struct master_service_haproxy_conn *hpconn)
+{
+	struct master_service *service = hpconn->service;
+	struct master_service_connection conn = hpconn->conn;
+
+	master_service_haproxy_conn_free(hpconn);
+	master_service_client_connection_callback(service, &conn);
+}
+
+static void
+master_service_haproxy_timeout(struct master_service_haproxy_conn *hpconn)
+{
+	i_error("haproxy: Client timed out (rip=%s)",
+		net_ip2addr(&hpconn->conn.remote_ip));
+	master_service_haproxy_conn_failure(hpconn);
+}
+
+static int
+master_service_haproxy_read(struct master_service_haproxy_conn *hpconn)
+{
+	static union {
+		unsigned char v1_data[HAPROXY_V1_MAX_HEADER_SIZE];
+		struct {
+			const struct haproxy_header_v2 hdr;
+			const struct haproxy_data_v2 data;
+		} v2;
+	} buf;
+	struct ip_addr *real_remote_ip = &hpconn->conn.remote_ip;
+	int fd = hpconn->conn.fd;
+	struct ip_addr local_ip, remote_ip;
+	unsigned int local_port, remote_port;
+	size_t size;
+	ssize_t ret;
+
+	/* the protocol specification explicitly states that the protocol header
+	   must be sent as one TCP frame, meaning that we will get it in full
+	   with the first recv() call.
+	   FIXME: still, it would be cleaner to allow reading it incrementally.
+	 */
+	do {
+		ret = recv(fd, &buf, sizeof(buf), MSG_PEEK);
+	} while (ret < 0 && errno == EINTR);
+
+	if (ret < 0 && errno == EAGAIN)
+		return 0;
+	if (ret <= 0) {
+		i_info("haproxy: Client disconnected (rip=%s)",
+		       net_ip2addr(real_remote_ip));
+		return -1;
+	}
+
+	/* don't update true connection data until we succeed */
+	local_ip = hpconn->conn.local_ip;
+	remote_ip = hpconn->conn.remote_ip;
+	local_port = hpconn->conn.local_port;
+	remote_port = hpconn->conn.remote_port;
+
+	/* protocol version 2 */
+	if (ret >= (ssize_t)sizeof(buf.v2.hdr) &&
+	    memcmp(buf.v2.hdr.sig, haproxy_v2sig,
+		   sizeof(buf.v2.hdr.sig)) == 0) {
+		const struct haproxy_header_v2 *hdr = &buf.v2.hdr;
+		const struct haproxy_data_v2 *data = &buf.v2.data;
+		size_t hdr_len;
+
+		if ((hdr->ver_cmd & 0xf0) != 0x20) {
+			i_error("haproxy: Client disconnected: "
+				"Unsupported protocol version (version=%02x, rip=%s)",
+				(hdr->ver_cmd & 0xf0) >> 4,
+				net_ip2addr(real_remote_ip));
+			return -1;
+		}
+
+		hdr_len = ntohs(hdr->len);
+		size = sizeof(*hdr) + hdr_len;
+		if (ret < (ssize_t)size) {
+			i_error("haproxy(v2): Client disconnected: "
+				"Protocol payload length does not match header "
+				"(got=%"PRIuSIZE_T", expect=%"PRIuSIZE_T", rip=%s)",
+				(size_t)ret, size, net_ip2addr(real_remote_ip));
+			return -1;
+		}
+
+		switch (hdr->ver_cmd & 0x0f) {
+		case HAPROXY_CMD_LOCAL:
+			/* keep local connection address for LOCAL */
+			/*i_debug("haproxy(v2): Local connection (rip=%s)",
+				net_ip2addr(real_remote_ip));*/
+			break;
+		case HAPROXY_CMD_PROXY:
+			if ((hdr->fam & 0x0f) != HAPROXY_SOCK_STREAM) {
+				/* UDP makes no sense currently */
+				i_error("haproxy(v2): Client disconnected: "
+					"Not using TCP (type=%02x, rip=%s)",
+					(hdr->fam & 0x0f), net_ip2addr(real_remote_ip));
+				return -1;
+			}
+			switch ((hdr->fam & 0xf0) >> 4) {
+			case HAPROXY_AF_INET:
+				/* IPv4 */
+				if (hdr_len < sizeof(data->addr.ip4)) {
+					i_error("haproxy(v2): Client disconnected: "
+						"IPv4 data is incomplete (rip=%s)",
+						net_ip2addr(real_remote_ip));
+					return -1;
+				}
+				local_ip.family = AF_INET;
+				local_ip.u.ip4.s_addr = data->addr.ip4.dst_addr;
+				local_port = ntohs(data->addr.ip4.dst_port);
+				remote_ip.family = AF_INET;
+				remote_ip.u.ip4.s_addr = data->addr.ip4.src_addr;
+				remote_port = ntohs(data->addr.ip4.src_port);
+				break;
+			case HAPROXY_AF_INET6:
+				/* IPv6 */
+				if (hdr_len < sizeof(data->addr.ip6)) {
+					i_error("haproxy(v2): Client disconnected: "
+						"IPv6 data is incomplete (rip=%s)",
+						net_ip2addr(real_remote_ip));
+					return -1;
+				}
+				local_ip.family = AF_INET6;
+				memcpy(&local_ip.u.ip6.s6_addr, data->addr.ip6.dst_addr, 16);
+				local_port = ntohs(data->addr.ip6.dst_port);
+				remote_ip.family = AF_INET6;
+				memcpy(&remote_ip.u.ip6.s6_addr, data->addr.ip6.src_addr, 16);
+				remote_port = ntohs(data->addr.ip6.src_port);
+				break;
+			case HAPROXY_AF_UNSPEC:
+			case HAPROXY_AF_UNIX:
+				/* unsupported; ignored */
+				i_error("haproxy(v2): Unsupported address family "
+					"(family=%02x, rip=%s)", (hdr->fam & 0xf0) >> 4,
+					net_ip2addr(real_remote_ip));
+				break;
+			default:
+				/* unsupported; error */
+				i_error("haproxy(v2): Client disconnected: "
+					"Unknown address family "
+					"(family=%02x, rip=%s)", (hdr->fam & 0xf0) >> 4,
+					net_ip2addr(real_remote_ip));
+				return -1;
+			}
+			break;
+		default:
+			i_error("haproxy(v2): Client disconnected: "
+				"Invalid command (cmd=%02x, rip=%s)",
+				(hdr->ver_cmd & 0x0f),
+				net_ip2addr(real_remote_ip));
+			return -1; /* not a supported command */
+		}
+
+		// FIXME: TLV vectors are ignored
+		//         (useful to see whether proxied client is using SSL)
+
+	/* protocol version 1 (soon obsolete) */
+	} else if (ret >= 8 && memcmp(buf.v1_data, "PROXY", 5) == 0) {
+		unsigned char *data = buf.v1_data, *end;
+		const char *const *fields;
+		unsigned int family = 0;
+
+		/* find end of header line */
+		end = memchr(data, '\r', ret - 1);
+		if (end == NULL || end[1] != '\n')
+			return -1;
+		*end = '\0';
+		size = end + 2 - data;
+
+		/* magic */
+		fields = t_strsplit((char *)data, " ");
+		i_assert(strcmp(*fields, "PROXY") == 0);
+		fields++;
+
+		/* protocol */
+		if (*fields == NULL) {
+			i_error("haproxy(v1): Client disconnected: "
+				"Field for proxied protocol is missing "
+				"(rip=%s)", net_ip2addr(real_remote_ip));
+			return -1;
+		}
+		if (strcmp(*fields, "TCP4") == 0) {
+			family = AF_INET;
+		} else if (strcmp(*fields, "TCP6") == 0) {
+			family = AF_INET6;
+		} else if (strcmp(*fields, "UNKNOWN") == 0) {
+			family = 0;
+		} else {
+			i_error("haproxy(v1): Client disconnected: "
+				"Unknown proxied protocol "


More information about the dovecot-cvs mailing list