[MEDIUM] add support for RDP cookie load-balancing

This patch adds support for hashing RDP cookies in order to
use them as a load-balancing key. The new "rdp-cookie(name)"
load-balancing metric has to be used for this. It is still
mandatory to wait for an RDP cookie in the frontend, otherwise
it will randomly work.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index e8fd090..3e0b7c3 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -956,6 +956,22 @@
                   specific headers such as 'Host'. For instance, in the Host
                   value "haproxy.1wt.eu", only "1wt" will be considered.
 
+      rdp-cookie
+      rdp-cookie(name)
+                  The RDP cookie <name> (or "mstshash" if omitted) will be
+                  looked up and hashed for each incoming TCP request. Just as
+                  with the equivalent ACL 'req_rdp_cookie()' function, the name
+                  is not case-sensitive. This mechanism is useful as a degraded
+                  persistence mode, as it makes it possible to always send the
+                  same user (or the same session ID) to the same server. If the
+                  cookie is not found, the normal round-robind algorithm is
+                  used instead.
+
+                  Note that for this to work, the frontend must ensure that an
+                  RDP cookie is already present in the request buffer. For this
+                  you must use 'tcp-request content accept' rule combined with
+                  a 'req_rdp_cookie_cnt' ACL.
+
     <arguments> is an optional list of arguments which may be needed by some
                 algorithms. Right now, only "url_param" and "uri" support an
                 optional argument.
diff --git a/include/types/backend.h b/include/types/backend.h
index 9f15c08..983d009 100644
--- a/include/types/backend.h
+++ b/include/types/backend.h
@@ -44,6 +44,7 @@
 #define BE_LB_ALGO_PH	(BE_LB_PROP_L7  | 0x04) /* balance on URL parameter hash */
 #define BE_LB_ALGO_LC	(BE_LB_PROP_DYN | 0x05) /* fast weighted leastconn mode (dynamic) */
 #define BE_LB_ALGO_HH	(BE_LB_PROP_L7  | 0x06) /* balance on Http Header value */
+#define BE_LB_ALGO_RCH	(BE_LB_PROP_L4  | 0x07) /* balance on RDP Cookie value */
 
 /* various constants */
 
diff --git a/src/backend.c b/src/backend.c
index bf9f789..d6d47fa 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1367,6 +1367,46 @@
 	return px->lbprm.map.srv[hash % px->lbprm.tot_weight];
 }
 
+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 acl_expr  expr;
+	struct acl_test  test;
+
+	/* tot_weight appears to mean srv_count */
+	if (px->lbprm.tot_weight == 0)
+		return NULL;
+
+	if (px->lbprm.map.state & PR_MAP_RECALC)
+		recalc_server_map(px);
+
+	memset(&expr, 0, sizeof(expr));
+	memset(&test, 0, sizeof(test));
+
+	expr.arg.str = px->hh_name;
+	expr.arg_len = px->hh_len;
+
+	ret = acl_fetch_rdp_cookie(px, s, NULL, ACL_DIR_REQ, &expr, &test);
+	if (ret == 0 || (test.flags & ACL_TEST_F_MAY_CHANGE) || test.len == 0)
+		return NULL;
+
+	/* Found a the hh_name in the headers.
+	 * we will compute the hash based on this value ctx.val.
+	 */
+	len = test.len;
+	p = (char *)test.ptr;
+	while (len) {
+		hash = *p + (hash << 6) + (hash << 16) - hash;
+		len--;
+		p++;
+	}
+
+	return px->lbprm.map.srv[hash % px->lbprm.tot_weight];
+}
  
 /*
  * This function applies the load-balancing algorithm to the session, as
@@ -1499,6 +1539,19 @@
 				}
 			}
 			break;
+		case BE_LB_ALGO_RCH:
+			/* RDP Cookie hashing */
+			s->srv = get_server_rch(s);
+
+			if (!s->srv) {
+				/* parameter not found, fall back to round robin on the map */
+				s->srv = get_server_rr_with_conns(s->be, s->prev_srv);
+				if (!s->srv) {
+					err = SRV_STATUS_FULL;
+					goto out;
+				}
+			}
+			break;
 		default:
 			/* unknown balancing algorithm */
 			err = SRV_STATUS_INTERNAL;
@@ -2205,9 +2258,38 @@
 			curproxy->hh_match_domain = 1;
 		}
 
+	}
+	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;
+
+			beg = args[0] + 11;
+			end = strchr(beg, ')');
+
+			if (!end || end == beg) {
+				snprintf(err, errlen, "'balance rdp-cookie(name)' requires an rdp 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 */
+			snprintf(err, errlen, "'balance rdp-cookie(name)' requires an rdp cookie name.");
+			return -1;
+		}
 	}
 	else {
-		snprintf(err, errlen, "'balance' only supports 'roundrobin', 'leastconn', 'source', 'uri', 'url_param' and 'hdr(name)' options.");
+		snprintf(err, errlen, "'balance' only supports 'roundrobin', 'leastconn', 'source', 'uri', 'url_param', 'hdr(name)' and 'rdp-cookie(name)' options.");
 		return -1;
 	}
 	return 0;