From e900e4bf383ce748850cb615d9554e3f5544d205 Mon Sep 17 00:00:00 2001
From: Bhaskar <bhaskar@tumblr.com>
Date: Tue, 29 Oct 2013 23:30:51 -0400
Subject: [PATCH] Enhance hash-type directive with an algorithm options

Summary:
In testing at tumblr, we found that using djb2 hashing instead of the
default sdbm hashing resulted is better workload distribution to our backends.

This commit implements a change, that allows the user to specify the hash
function/algorithm they want to use. It does not limit itself to consistent
hashing scenarios.

The supported hash functions/algorithms are sdbm (default), djb2, wt6.

For a discussion of the feature and analysis, see mailing list thread
"Consistent hashing alternative to sdbm".

Note: This change does NOT make changes to new features, for instance,
applying an avalance hashing always being performed before applying
consistent hashing.
---
 Makefile                |   2 +-
 doc/configuration.txt   |  85 +++++++++------
 include/common/hash.h   |  29 ++++++
 include/types/backend.h |   7 ++
 src/backend.c           | 272 ++++++++++++++++++++++++++----------------------
 src/cfgparse.c          |  19 ++++
 src/hash.c              |  87 ++++++++++++++++
 7 files changed, 341 insertions(+), 160 deletions(-)
 create mode 100644 include/common/hash.h
 create mode 100644 src/hash.c

diff --git a/Makefile b/Makefile
index 5e12af8..b4feede 100644
--- a/Makefile
+++ b/Makefile
@@ -635,7 +635,7 @@ OBJS = src/haproxy.o src/sessionhash.o src/base64.o src/protocol.o \
        src/stream_interface.o src/dumpstats.o src/proto_tcp.o \
        src/session.o src/hdr_idx.o src/ev_select.o src/signal.o \
        src/acl.o src/sample.o src/memory.o src/freq_ctr.o src/auth.o \
-       src/compression.o src/payload.o
+       src/compression.o src/payload.o src/hash.o
 
 EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o \
               $(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \
diff --git a/doc/configuration.txt b/doc/configuration.txt
index dddb07e..2febf90 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2483,45 +2483,64 @@ grace <time>
   simplify it.
 
 
-hash-type <method>
+hash-type <method> <algorithm>
   Specify a method to use for mapping hashes to servers
   May be used in sections :   defaults | frontend | listen | backend
                                  yes   |    no    |   yes  |   yes
   Arguments :
-    map-based   the hash table is a static array containing all alive servers.
-                The hashes will be very smooth, will consider weights, but will
-                be static in that weight changes while a server is up will be
-                ignored. This means that there will be no slow start. Also,
-                since a server is selected by its position in the array, most
-                mappings are changed when the server count changes. This means
-                that when a server goes up or down, or when a server is added
-                to a farm, most connections will be redistributed to different
-                servers. This can be inconvenient with caches for instance.
-
-    avalanche   this mechanism uses the default map-based hashing described
-                above but applies a full avalanche hash before performing the
-                mapping. The result is a slightly less smooth hash for most
-                situations, but the hash becomes better than pure map-based
-                hashes when the number of servers is a multiple of the size of
-                the input set. When using URI hash with a number of servers
-                multiple of 64, it's desirable to change the hash type to
-                this value.
-
-    consistent  the hash table is a tree filled with many occurrences of each
-                server. The hash key is looked up in the tree and the closest
-                server is chosen. This hash is dynamic, it supports changing
-                weights while the servers are up, so it is compatible with the
-                slow start feature. It has the advantage that when a server
-                goes up or down, only its associations are moved. When a server
-                is added to the farm, only a few part of the mappings are
-                redistributed, making it an ideal algorithm for caches.
-                However, due to its principle, the algorithm will never be very
-                smooth and it may sometimes be necessary to adjust a server's
-                weight or its ID to get a more balanced distribution. In order
-                to get the same distribution on multiple load balancers, it is
-                important that all servers have the same IDs.
+    <method> is the method used to select a server from the hash computed
+             by the <algorithm>
+
+      map-based   the hash table is a static array containing all alive servers.
+                  The hashes will be very smooth, will consider weights, but will
+                  be static in that weight changes while a server is up will be
+                  ignored. This means that there will be no slow start. Also,
+                  since a server is selected by its position in the array, most
+                  mappings are changed when the server count changes. This means
+                  that when a server goes up or down, or when a server is added
+                  to a farm, most connections will be redistributed to different
+                  servers. This can be inconvenient with caches for instance.
+
+      avalanche   this mechanism uses the default map-based hashing described
+                  above but applies a full avalanche hash before performing the
+                  mapping. The result is a slightly less smooth hash for most
+                  situations, but the hash becomes better than pure map-based
+                  hashes when the number of servers is a multiple of the size of
+                  the input set. When using URI hash with a number of servers
+                  multiple of 64, it's desirable to change the hash type to
+                  this value.
+
+      consistent  the hash table is a tree filled with many occurrences of each
+                  server. The hash key is looked up in the tree and the closest
+                  server is chosen. This hash is dynamic, it supports changing
+                  weights while the servers are up, so it is compatible with the
+                  slow start feature. It has the advantage that when a server
+                  goes up or down, only its associations are moved. When a server
+                  is added to the farm, only a few part of the mappings are
+                  redistributed, making it an ideal algorithm for caches.
+                  However, due to its principle, the algorithm will never be very
+                  smooth and it may sometimes be necessary to adjust a server's
+                  weight or its ID to get a more balanced distribution. In order
+                  to get the same distribution on multiple load balancers, it is
+                  important that all servers have the same IDs. Note: An avalanche
+                  hash is always performed before applying the consistent hash.
+
+     <algorithm> is the algorithm used for hashing.
+
+       sdbm   this algorithm was created for sdbm (a public-domain reimplementation 
+              of ndbm) database library. it was found to do well in scrambling bits,
+              causing better distribution of the keys and fewer splits. it also 
+              happens to be a good general hashing function with good distribution.
+
+       djb2   this algorithm was first reported by dan bernstein many years ago in 
+              comp.lang.c. Studied has shown that for certain workload this function
+              provides a better distribution then sdbm.
+
+       wt6    TBD
 
   The default hash type is "map-based" and is recommended for most usages.
+  The default algorithm is "sdbm", selection of algorithm should be based on the key
+  range of the values being hashed.
 
   See also : "balance", "server"
 
diff --git a/include/common/hash.h b/include/common/hash.h
new file mode 100644
index 0000000..798cc3d
--- /dev/null
+++ b/include/common/hash.h
@@ -0,0 +1,29 @@
+/*
+ * include/common/hash.h
+ * Macros for different hashing function.
+ *
+ * Copyright (C) 2000-2011 Willy Tarreau - w@1wt.eu
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _COMMON_HASH_H_
+#define _COMMON_HASH_H_
+
+unsigned long djb2(const char *key, int len);
+unsigned long wt6 (const char *key, int len);
+unsigned long sdbm(const char *key, int len);
+
+#endif /* _COMMON_HASH_H_ */
diff --git a/include/types/backend.h b/include/types/backend.h
index fd66e40..0e9984e 100644
--- a/include/types/backend.h
+++ b/include/types/backend.h
@@ -46,6 +46,13 @@
 #define BE_LB_HASH_PRM  0x00002  /* hash HTTP URL parameter */
 #define BE_LB_HASH_HDR  0x00003  /* hash HTTP header value */
 #define BE_LB_HASH_RDP  0x00004  /* hash RDP cookie value */
+#define BE_LB_HASH_INP  0x0000F  /* get/clear the has input param */
+
+/* BE_LB_HF_* is used with BE_LB_HASH_TYPE */
+#define BE_LB_HF_SDBM   0x00000  /* sdbm hash */
+#define BE_LB_HF_DJB2   0x00010  /* djb2 hash */
+#define BE_LB_HF_WT6    0x00020  /* wt6 hash */
+#define BE_LB_HF_TYPE   0x00030  /* get/clear hash types */
 
 /* BE_LB_RR_* is used with BE_LB_KIND_RR */
 #define BE_LB_RR_DYN    0x00000  /* dynamic round robin (default) */
diff --git a/src/backend.c b/src/backend.c
index 0392355..767c43d 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -23,6 +23,7 @@
 #include <common/compat.h>
 #include <common/config.h>
 #include <common/debug.h>
+#include <common/hash.h>
 #include <common/ticks.h>
 #include <common/time.h>
 
@@ -51,6 +52,19 @@
 #include <proto/stream_interface.h>
 #include <proto/task.h>
 
+/* helper function to invoke the correct hash method */
+unsigned long gen_hash(const char* key, unsigned long len, const struct proxy* px) {
+	unsigned long hash = 0;
+	if (px->lbprm.algo & BE_LB_HF_DJB2)
+		hash = djb2(key, len);
+	else if (px->lbprm.algo & BE_LB_HF_WT6)
+		hash = wt6(key, len);
+	else
+		hash = sdbm(key, len);
+
+	return hash;
+}
+
 /*
  * This function recounts the number of usable active and backup servers for
  * proxy <p>. These numbers are returned into the p->srv_act and p->srv_bck.
@@ -153,6 +167,7 @@ struct server *get_server_uh(struct proxy *px, char *uri, int uri_len)
 	unsigned long hash = 0;
 	int c;
 	int slashes = 0;
+	const char *start, *end;
 
 	if (px->lbprm.tot_weight == 0)
 		return NULL;
@@ -163,9 +178,9 @@ struct server *get_server_uh(struct proxy *px, char *uri, int uri_len)
 
 	if (px->uri_len_limit)
 		uri_len = MIN(uri_len, px->uri_len_limit);
-
+	start = end = uri;
 	while (uri_len--) {
-		c = *uri++;
+		c = *end++;
 		if (c == '/') {
 			slashes++;
 			if (slashes == px->uri_dirs_depth1) /* depth+1 */
@@ -173,9 +188,10 @@ struct server *get_server_uh(struct proxy *px, char *uri, int uri_len)
 		}
 		else if (c == '?' && !px->uri_whole)
 			break;
-
-		hash = c + (hash << 6) + (hash << 16) - hash;
 	}
+
+	hash = gen_hash(start, (end - start), px);
+
 	if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
 		hash = full_hash(hash);
  hash_done:
@@ -197,6 +213,7 @@ struct server *get_server_uh(struct proxy *px, char *uri, int uri_len)
 struct server *get_server_ph(struct proxy *px, const char *uri, int uri_len)
 {
 	unsigned long hash = 0;
+	const char *start, *end;
 	const char *p;
 	const char *params;
 	int plen;
@@ -223,15 +240,18 @@ struct server *get_server_ph(struct proxy *px, const char *uri, int uri_len)
 				 * skip the equal symbol
 				 */
 				p += plen + 1;
+				start = end = p;
 				uri_len -= plen + 1;
 
-				while (uri_len && *p != '&') {
-					hash = *p + (hash << 6) + (hash << 16) - hash;
+				while (uri_len && *end != '&') {
 					uri_len--;
-					p++;
+					end++;
 				}
+				hash = gen_hash(start, (end - start), px);
+
 				if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
 					hash = full_hash(hash);
+
 				if (px->lbprm.algo & BE_LB_LKUP_CHTREE)
 					return chash_get_server_hash(px, hash);
 				else
@@ -263,6 +283,7 @@ struct server *get_server_ph_post(struct session *s)
 	unsigned long    len  = msg->body_len;
 	const char      *params = b_ptr(req->buf, (int)(msg->sov - req->buf->o));
 	const char      *p    = params;
+	const char      *start, *end;
 
 	if (len > buffer_len(req->buf) - msg->sov)
 		len = buffer_len(req->buf) - msg->sov;
@@ -282,12 +303,13 @@ struct server *get_server_ph_post(struct session *s)
 				 * skip the equal symbol
 				 */
 				p += plen + 1;
+				start = end = p;
 				len -= plen + 1;
 
-				while (len && *p != '&') {
+				while (len && *end != '&') {
 					if (unlikely(!HTTP_IS_TOKEN(*p))) {
 						/* if in a POST, body must be URI encoded or it's not a URI.
-						 * Do not interprete any possible binary data as a parameter.
+						 * Do not interpret any possible binary data as a parameter.
 						 */
 						if (likely(HTTP_IS_LWS(*p))) /* eol, uncertain uri len */
 							break;
@@ -295,13 +317,15 @@ struct server *get_server_ph_post(struct session *s)
 									      * This body does not contain parameters.
 									      */
 					}
-					hash = *p + (hash << 6) + (hash << 16) - hash;
 					len--;
-					p++;
+					end++;
 					/* should we break if vlen exceeds limit? */
 				}
+				hash = gen_hash(start, (end - start), px);
+
 				if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
 					hash = full_hash(hash);
+
 				if (px->lbprm.algo & BE_LB_LKUP_CHTREE)
 					return chash_get_server_hash(px, hash);
 				else
@@ -338,6 +362,7 @@ struct server *get_server_hh(struct session *s)
 	unsigned long    len;
 	struct hdr_ctx   ctx;
 	const char      *p;
+	const char *start, *end;
 
 	/* tot_weight appears to mean srv_count */
 	if (px->lbprm.tot_weight == 0)
@@ -362,14 +387,11 @@ struct server *get_server_hh(struct session *s)
 	len = ctx.vlen;
 	p = (char *)ctx.line + ctx.val;
 	if (!px->hh_match_domain) {
-		while (len) {
-			hash = *p + (hash << 6) + (hash << 16) - hash;
-			len--;
-			p++;
-		}
+		hash = gen_hash(p, len, px);
 	} else {
 		int dohash = 0;
 		p += len - 1;
+		start = end = p;
 		/* special computation, use only main domain name, not tld/host
 		 * going back from the end of string, start hashing at first
 		 * dot stop at next.
@@ -378,17 +400,20 @@ struct server *get_server_hh(struct session *s)
 		 */
 		while (len) {
 			if (*p == '.') {
-				if (!dohash)
+				if (!dohash) {
 					dohash = 1;
+					start = end = p - 1;
+				}
 				else
 					break;
 			} else {
 				if (dohash)
-					hash = *p + (hash << 6) + (hash << 16) - hash;
+					start--;
 			}
 			len--;
 			p--;
 		}
+		hash = gen_hash(start, (end - start), px);
 	}
 	if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
 		hash = full_hash(hash);
@@ -405,7 +430,6 @@ struct server *get_server_rch(struct session *s)
 	unsigned long    hash = 0;
 	struct proxy    *px   = s->be;
 	unsigned long    len;
-	const char      *p;
 	int              ret;
 	struct sample    smp;
 	struct arg       args[2];
@@ -439,12 +463,8 @@ struct server *get_server_rch(struct session *s)
 	/* Found a the hh_name in the headers.
 	 * we will compute the hash based on this value ctx.val.
 	 */
-	p = smp.data.str.str;
-	while (len) {
-		hash = *p + (hash << 6) + (hash << 16) - hash;
-		len--;
-		p++;
-	}
+	hash = gen_hash(smp.data.str.str, len, px);
+
 	if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
 		hash = full_hash(hash);
  hash_done:
@@ -548,7 +568,7 @@ int assign_server(struct session *s)
 				goto out;
 			}
 
-			switch (s->be->lbprm.algo & BE_LB_PARM) {
+			switch (s->be->lbprm.algo & BE_LB_HASH_INP) {
 			case BE_LB_HASH_SRC:
 				if (s->req->prod->conn->addr.from.ss_family == AF_INET) {
 					srv = get_server_sh(s->be,
@@ -1235,136 +1255,136 @@ int backend_parse_balance(const char **args, char **err, struct proxy *curproxy)
 		curproxy->lbprm.algo &= ~BE_LB_ALGO;
 		curproxy->lbprm.algo |= BE_LB_ALGO_LC;
 	}
-	else if (!strcmp(args[0], "source")) {
-		curproxy->lbprm.algo &= ~BE_LB_ALGO;
-		curproxy->lbprm.algo |= BE_LB_ALGO_SH;
-	}
-	else if (!strcmp(args[0], "uri")) {
-		int arg = 1;
+	else {
+		/* clear the algorithm without discard hash function type */
+		curproxy->lbprm.algo &= (~BE_LB_ALGO | (curproxy->lbprm.algo & BE_LB_HF_TYPE));
 
-		curproxy->lbprm.algo &= ~BE_LB_ALGO;
-		curproxy->lbprm.algo |= BE_LB_ALGO_UH;
+		if (!strcmp(args[0], "source")) {
+			curproxy->lbprm.algo |= BE_LB_ALGO_SH;
+		}
+		else if (!strcmp(args[0], "uri")) {
+			int arg = 1;
 
-		curproxy->uri_whole = 0;
+			curproxy->lbprm.algo |= BE_LB_ALGO_UH;
 
-		while (*args[arg]) {
-			if (!strcmp(args[arg], "len")) {
-				if (!*args[arg+1] || (atoi(args[arg+1]) <= 0)) {
-					memprintf(err, "%s : '%s' expects a positive integer (got '%s').", args[0], args[arg], args[arg+1]);
-					return -1;
+			curproxy->uri_whole = 0;
+
+			while (*args[arg]) {
+				if (!strcmp(args[arg], "len")) {
+					if (!*args[arg+1] || (atoi(args[arg+1]) <= 0)) {
+						memprintf(err, "%s : '%s' expects a positive integer (got '%s').", args[0], args[arg], args[arg+1]);
+						return -1;
+					}
+					curproxy->uri_len_limit = atoi(args[arg+1]);
+					arg += 2;
 				}
-				curproxy->uri_len_limit = atoi(args[arg+1]);
-				arg += 2;
-			}
-			else if (!strcmp(args[arg], "depth")) {
-				if (!*args[arg+1] || (atoi(args[arg+1]) <= 0)) {
-					memprintf(err, "%s : '%s' expects a positive integer (got '%s').", args[0], args[arg], args[arg+1]);
+				else if (!strcmp(args[arg], "depth")) {
+					if (!*args[arg+1] || (atoi(args[arg+1]) <= 0)) {
+						memprintf(err, "%s : '%s' expects a positive integer (got '%s').", args[0], args[arg], args[arg+1]);
+						return -1;
+					}
+					/* hint: we store the position of the ending '/' (depth+1) so
+					 * that we avoid a comparison while computing the hash.
+					 */
+					curproxy->uri_dirs_depth1 = atoi(args[arg+1]) + 1;
+					arg += 2;
+				}
+				else if (!strcmp(args[arg], "whole")) {
+					curproxy->uri_whole = 1;
+					arg += 1;
+				}
+				else {
+					memprintf(err, "%s only accepts parameters 'len', 'depth', and 'whole' (got '%s').", args[0], args[arg]);
 					return -1;
 				}
-				/* hint: we store the position of the ending '/' (depth+1) so
-				 * that we avoid a comparison while computing the hash.
-				 */
-				curproxy->uri_dirs_depth1 = atoi(args[arg+1]) + 1;
-				arg += 2;
 			}
-			else if (!strcmp(args[arg], "whole")) {
-				curproxy->uri_whole = 1;
-				arg += 1;
-			}
-			else {
-				memprintf(err, "%s only accepts parameters 'len', 'depth', and 'whole' (got '%s').", args[0], args[arg]);
-				return -1;
-			}
-		}
-	}
-	else if (!strcmp(args[0], "url_param")) {
-		if (!*args[1]) {
-			memprintf(err, "%s requires an URL parameter name.", args[0]);
-			return -1;
 		}
-		curproxy->lbprm.algo &= ~BE_LB_ALGO;
-		curproxy->lbprm.algo |= BE_LB_ALGO_PH;
-
-		free(curproxy->url_param_name);
-		curproxy->url_param_name = strdup(args[1]);
-		curproxy->url_param_len  = strlen(args[1]);
-		if (*args[2]) {
-			if (strcmp(args[2], "check_post")) {
-				memprintf(err, "%s only accepts 'check_post' modifier (got '%s').", args[0], args[2]);
+		else if (!strcmp(args[0], "url_param")) {
+			if (!*args[1]) {
+				memprintf(err, "%s requires an URL parameter name.", args[0]);
 				return -1;
 			}
-			if (*args[3]) {
-				/* TODO: maybe issue a warning if there is no value, no digits or too long */
-				curproxy->url_param_post_limit = str2ui(args[3]);
+			curproxy->lbprm.algo |= BE_LB_ALGO_PH;
+
+			free(curproxy->url_param_name);
+			curproxy->url_param_name = strdup(args[1]);
+			curproxy->url_param_len  = strlen(args[1]);
+			if (*args[2]) {
+				if (strcmp(args[2], "check_post")) {
+					memprintf(err, "%s only accepts 'check_post' modifier (got '%s').", args[0], args[2]);
+					return -1;
+				}
+				if (*args[3]) {
+					/* TODO: maybe issue a warning if there is no value, no digits or too long */
+					curproxy->url_param_post_limit = str2ui(args[3]);
+				}
+				/* if no limit, or faul value in args[3], then default to a moderate wordlen */
+				if (!curproxy->url_param_post_limit)
+					curproxy->url_param_post_limit = 48;
+				else if ( curproxy->url_param_post_limit < 3 )
+					curproxy->url_param_post_limit = 3; /* minimum example: S=3 or \r\nS=6& */
 			}
-			/* if no limit, or faul value in args[3], then default to a moderate wordlen */
-			if (!curproxy->url_param_post_limit)
-				curproxy->url_param_post_limit = 48;
-			else if ( curproxy->url_param_post_limit < 3 )
-				curproxy->url_param_post_limit = 3; /* minimum example: S=3 or \r\nS=6& */
 		}
-	}
-	else if (!strncmp(args[0], "hdr(", 4)) {
-		const char *beg, *end;
+		else if (!strncmp(args[0], "hdr(", 4)) {
+			const char *beg, *end;
 
-		beg = args[0] + 4;
-		end = strchr(beg, ')');
+			beg = args[0] + 4;
+			end = strchr(beg, ')');
 
-		if (!end || end == beg) {
-			memprintf(err, "hdr requires an http header field name.");
-			return -1;
-		}
+			if (!end || end == beg) {
+				memprintf(err, "hdr requires an http header field name.");
+				return -1;
+			}
 
-		curproxy->lbprm.algo &= ~BE_LB_ALGO;
-		curproxy->lbprm.algo |= BE_LB_ALGO_HH;
+			curproxy->lbprm.algo |= BE_LB_ALGO_HH;
 
-		free(curproxy->hh_name);
-		curproxy->hh_len  = end - beg;
-		curproxy->hh_name = my_strndup(beg, end - beg);
-		curproxy->hh_match_domain = 0;
+			free(curproxy->hh_name);
+			curproxy->hh_len  = end - beg;
+			curproxy->hh_name = my_strndup(beg, end - beg);
+			curproxy->hh_match_domain = 0;
 
-		if (*args[1]) {
-			if (strcmp(args[1], "use_domain_only")) {
-				memprintf(err, "%s only accepts 'use_domain_only' modifier (got '%s').", args[0], args[1]);
-				return -1;
+			if (*args[1]) {
+				if (strcmp(args[1], "use_domain_only")) {
+					memprintf(err, "%s only accepts 'use_domain_only' modifier (got '%s').", args[0], args[1]);
+					return -1;
+				}
+				curproxy->hh_match_domain = 1;
 			}
-			curproxy->hh_match_domain = 1;
+
 		}
+		else if (!strncmp(args[0], "rdp-cookie", 10)) {
+			curproxy->lbprm.algo |= BE_LB_ALGO_RCH;
 
-	}
-	else if (!strncmp(args[0], "rdp-cookie", 10)) {
-		curproxy->lbprm.algo &= ~BE_LB_ALGO;
-		curproxy->lbprm.algo |= BE_LB_ALGO_RCH;
+			if ( *(args[0] + 10 ) == '(' ) { /* cookie name */
+				const char *beg, *end;
 
-		if ( *(args[0] + 10 ) == '(' ) { /* cookie name */
-			const char *beg, *end;
+				beg = args[0] + 11;
+				end = strchr(beg, ')');
 
-			beg = args[0] + 11;
-			end = strchr(beg, ')');
+				if (!end || end == beg) {
+					memprintf(err, "rdp-cookie : missing cookie name.");
+					return -1;
+				}
 
-			if (!end || end == beg) {
+				free(curproxy->hh_name);
+				curproxy->hh_name = my_strndup(beg, end - beg);
+				curproxy->hh_len  = end - beg;
+			}
+			else if ( *(args[0] + 10 ) == '\0' ) { /* default cookie name 'mstshash' */
+				free(curproxy->hh_name);
+				curproxy->hh_name = strdup("mstshash");
+				curproxy->hh_len  = strlen(curproxy->hh_name);
+			}
+			else { /* syntax */
 				memprintf(err, "rdp-cookie : missing cookie name.");
 				return -1;
 			}
-
-			free(curproxy->hh_name);
-			curproxy->hh_name = my_strndup(beg, end - beg);
-			curproxy->hh_len  = end - beg;
 		}
-		else if ( *(args[0] + 10 ) == '\0' ) { /* default cookie name 'mstshash' */
-			free(curproxy->hh_name);
-			curproxy->hh_name = strdup("mstshash");
-			curproxy->hh_len  = strlen(curproxy->hh_name);
-		}
-		else { /* syntax */
-			memprintf(err, "rdp-cookie : missing cookie name.");
+		else {
+			memprintf(err, "only supports 'roundrobin', 'static-rr', 'leastconn', 'source', 'uri', 'url_param', 'hdr(name)' and 'rdp-cookie(name)' options.");
 			return -1;
 		}
 	}
-	else {
-		memprintf(err, "only supports 'roundrobin', 'static-rr', 'leastconn', 'source', 'uri', 'url_param', 'hdr(name)' and 'rdp-cookie(name)' options.");
-		return -1;
-	}
 	return 0;
 }
 
diff --git a/src/cfgparse.c b/src/cfgparse.c
index b3435ef..69c5c3a 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -4105,6 +4105,25 @@ stats_error_parsing:
 			err_code |= ERR_ALERT | ERR_FATAL;
 			goto out;
 		}
+
+		/* set the hash function to use */
+		if (!*args[2]) {
+			curproxy->lbprm.algo &= ~BE_LB_HF_TYPE;
+			curproxy->lbprm.algo |= BE_LB_HF_SDBM;
+		} else if (!strcmp(args[2], "sdbm")) {
+			curproxy->lbprm.algo &= ~BE_LB_HF_TYPE;
+			curproxy->lbprm.algo |= BE_LB_HF_SDBM;
+		} else if (!strcmp(args[2], "djb2")) {
+			curproxy->lbprm.algo &= ~BE_LB_HF_TYPE;
+			curproxy->lbprm.algo |= BE_LB_HF_DJB2;
+		} else if (!strcmp(args[2], "wt6")) {
+			curproxy->lbprm.algo &= ~BE_LB_HF_TYPE;
+			curproxy->lbprm.algo |= BE_LB_HF_WT6;
+		} else {
+			Alert("parsing [%s:%d] : '%s' only supports 'sdbm', 'djb2' and 'wt6' hash functions.\n", file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
 	}
 	else if (!strcmp(args[0], "server") || !strcmp(args[0], "default-server")) {  /* server address */
 		int cur_arg;
diff --git a/src/hash.c b/src/hash.c
new file mode 100644
index 0000000..afba697
--- /dev/null
+++ b/src/hash.c
@@ -0,0 +1,87 @@
+/*
+ * Hash function implementation
+ *
+ * See mailing list thread on "Consistent hashing alternative to sdbm"
+ * http://marc.info/?l=haproxy&m=138213693909219
+ *
+ * Copyright 2000-2010 Willy Tarreau <w@1wt.eu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+
+#include <common/hash.h>
+
+unsigned long wt6(const char *key, int len)
+{
+	unsigned h0 = 0xa53c965aUL;
+	unsigned h1 = 0x5CA6953AUL;
+	unsigned step0 = 6;
+	unsigned step1 = 18;
+
+	for (; len > 0; len--) {
+		unsigned int t;
+
+		t = ((unsigned int)*key);
+		key++;
+
+		h0 = ~(h0 ^ t);
+		h1 = ~(h1 + t);
+
+		t  = (h1 << step0) | (h1 >> (32-step0));
+		h1 = (h0 << step1) | (h0 >> (32-step1));
+		h0 = t;
+
+		t = ((h0 >> 16) ^ h1) & 0xffff;
+		step0 = t & 0x1F;
+		step1 = t >> 11;
+	}
+	return h0 ^ h1;
+}
+
+unsigned long djb2(const char *key, int len)
+{
+	unsigned long hash = 5381;
+
+	/* the hash unrolled eight times */
+	for (; len >= 8; len -= 8) {
+		hash = ((hash << 5) + hash) + *key++;
+		hash = ((hash << 5) + hash) + *key++;
+		hash = ((hash << 5) + hash) + *key++;
+		hash = ((hash << 5) + hash) + *key++;
+		hash = ((hash << 5) + hash) + *key++;
+		hash = ((hash << 5) + hash) + *key++;
+		hash = ((hash << 5) + hash) + *key++;
+		hash = ((hash << 5) + hash) + *key++;
+	}
+	switch (len) {
+		case 7: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */
+		case 6: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */
+		case 5: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */
+		case 4: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */
+		case 3: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */
+		case 2: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */
+		case 1: hash = ((hash << 5) + hash) + *key++; break;
+		default: /* case 0: */ break;
+	}
+	return hash;
+}
+
+unsigned long sdbm(const char *key, int len)
+{
+	unsigned long hash = 0;
+	int c;
+
+	while (len--) {
+		c = *key++;
+		hash = c + (hash << 6) + (hash << 16) - hash;
+	}
+
+	return hash;
+}
+
+
-- 
1.8.2.1

