diff --git a/doc/configuration.txt b/doc/configuration.txt
index 6627548ed..c1ba462ec 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -3986,6 +3986,19 @@ balance url_param [check_post]
turn new servers on when the queue inflates. Alternatively,
using "http-check send-state" may inform servers on the load.
+ hash Takes a regular sample expression in argument. The expression
+ is evaluated for each request and hashed according to the
+ configured hash-type. The result of the hash is divided by
+ the total weight of the running servers to designate which
+ server will receive the request. This can be used in place of
+ "source", "uri", "hdr()", "url_param()", "rdp-cookie" to make
+ use of a converter, refine the evaluation, or be used to
+ extract data from local variables for example. When the data
+ is not available, round robin will apply. This algorithm is
+ static by default, which means that changing a server's
+ weight on the fly will have no effect, but this can be
+ changed using "hash-type".
+
source The source IP address is hashed and divided by the total
weight of the running servers to designate which server will
receive the request. This ensures that the same client IP
@@ -3998,7 +4011,7 @@ balance url_param [check_post]
to clients which refuse session cookies. This algorithm is
static by default, which means that changing a server's
weight on the fly will have no effect, but this can be
- changed using "hash-type".
+ changed using "hash-type". See also the "hash" option above.
uri This algorithm hashes either the left part of the URI (before
the question mark) or the whole URI (if the "whole" parameter
@@ -4030,7 +4043,8 @@ balance url_param [check_post]
A "path-only" parameter indicates that the hashing key starts
at the first '/' of the path. This can be used to ignore the
authority part of absolute URIs, and to make sure that HTTP/1
- and HTTP/2 URIs will provide the same hash.
+ and HTTP/2 URIs will provide the same hash. See also the
+ "hash" option above.
url_param The URL parameter specified in argument will be looked up in
the query string of each HTTP GET request.
@@ -4058,7 +4072,8 @@ balance url_param [check_post]
applied. Note that this algorithm may only be used in an HTTP
backend. This algorithm is static by default, which means
that changing a server's weight on the fly will have no
- effect, but this can be changed using "hash-type".
+ effect, but this can be changed using "hash-type". See also
+ the "hash" option above.
hdr() The HTTP header will be looked up in each HTTP
request. Just as with the equivalent ACL 'hdr()' function,
@@ -4073,7 +4088,8 @@ balance url_param [check_post]
This algorithm is static by default, which means that
changing a server's weight on the fly will have no effect,
- but this can be changed using "hash-type".
+ but this can be changed using "hash-type". See also the
+ "hash" option above.
random
random()
@@ -4121,7 +4137,8 @@ balance url_param [check_post]
This algorithm is static by default, which means that
changing a server's weight on the fly will have no effect,
- but this can be changed using "hash-type".
+ but this can be changed using "hash-type". See also the
+ "hash" option above.
is an optional list of arguments which may be needed by some
algorithms. Right now, only "url_param" and "uri" support an
@@ -4143,6 +4160,9 @@ balance url_param [check_post]
balance hdr(User-Agent)
balance hdr(host)
balance hdr(Host) use_domain_only
+ balance hash req.cookie(clientid)
+ balance hash var(req.client_id)
+ balance hash req.hdr_ip(x-forwarded-for,-1),ipmask(24)
Note: the following caveats and limitations on using the "check_post"
extension with "url_param" must be considered :
diff --git a/include/haproxy/backend-t.h b/include/haproxy/backend-t.h
index 126528400..c06bdbe73 100644
--- a/include/haproxy/backend-t.h
+++ b/include/haproxy/backend-t.h
@@ -47,6 +47,7 @@
#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_SMP 0x00005 /* hash a sample expression */
#define BE_LB_HASH_RND 0x00008 /* hash a random value */
/* BE_LB_RR_* is used with BE_LB_KIND_RR */
@@ -89,6 +90,7 @@
#define BE_LB_ALGO_PH (BE_LB_KIND_HI | BE_LB_NEED_HTTP | BE_LB_HASH_PRM) /* hash: HTTP URL parameter */
#define BE_LB_ALGO_HH (BE_LB_KIND_HI | BE_LB_NEED_HTTP | BE_LB_HASH_HDR) /* hash: HTTP header value */
#define BE_LB_ALGO_RCH (BE_LB_KIND_HI | BE_LB_NEED_DATA | BE_LB_HASH_RDP) /* hash: RDP cookie value */
+#define BE_LB_ALGO_SMP (BE_LB_KIND_HI | BE_LB_NEED_DATA | BE_LB_HASH_SMP) /* hash: sample expression */
#define BE_LB_ALGO (BE_LB_KIND | BE_LB_NEED | BE_LB_PARM ) /* mask to clear algo */
/* Higher bits define how a given criterion is mapped to a server. In fact it
@@ -152,6 +154,7 @@ struct lbprm {
int wmult; /* ratio between user weight and effective weight */
int wdiv; /* ratio between effective weight and user weight */
int hash_balance_factor; /* load balancing factor * 100, 0 if disabled */
+ struct sample_expr *expr; /* sample expression for "balance hash" */
char *arg_str; /* name of the URL parameter/header/cookie used for hashing */
int arg_len; /* strlen(arg_str), computed only once */
int arg_opt1; /* extra option 1 for the LB algo (algo-specific) */
diff --git a/src/backend.c b/src/backend.c
index 18c84f4e9..c4a0868f3 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -524,6 +524,40 @@ static struct server *get_server_rch(struct stream *s, const struct server *avoi
return map_get_server_hash(px, hash);
}
+/* sample expression HASH. Returns NULL if the sample is not found or if there
+ * are no server, relying on the caller to fall back to round robin instead.
+ */
+static struct server *get_server_expr(struct stream *s, const struct server *avoid)
+{
+ struct proxy *px = s->be;
+ struct sample *smp;
+ unsigned int hash = 0;
+
+ if (px->lbprm.tot_weight == 0)
+ return NULL;
+
+ /* note: no need to hash if there's only one server left */
+ if (px->lbprm.tot_used == 1)
+ goto hash_done;
+
+ smp = sample_fetch_as_type(px, s->sess, s, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, px->lbprm.expr, SMP_T_BIN);
+ if (!smp)
+ return NULL;
+
+ /* We have the desired data. Let's hash it according to the configured
+ * options and algorithm.
+ */
+ hash = gen_hash(px, smp->data.u.str.area, smp->data.u.str.data);
+
+ if ((px->lbprm.algo & BE_LB_HASH_MOD) == BE_LB_HMOD_AVAL)
+ hash = full_hash(hash);
+ hash_done:
+ if ((px->lbprm.algo & BE_LB_LKUP) == BE_LB_LKUP_CHTREE)
+ return chash_get_server_hash(px, hash, avoid);
+ else
+ return map_get_server_hash(px, hash);
+}
+
/* random value */
static struct server *get_server_rnd(struct stream *s, const struct server *avoid)
{
@@ -760,6 +794,11 @@ int assign_server(struct stream *s)
srv = get_server_rch(s, prev_srv);
break;
+ case BE_LB_HASH_SMP:
+ /* sample expression hashing */
+ srv = get_server_expr(s, prev_srv);
+ break;
+
default:
/* unknown balancing algorithm */
err = SRV_STATUS_INTERNAL;
@@ -2578,6 +2617,8 @@ const char *backend_lb_algo_str(int algo) {
return "hdr";
else if (algo == BE_LB_ALGO_RCH)
return "rdp-cookie";
+ else if (algo == BE_LB_ALGO_SMP)
+ return "hash";
else if (algo == BE_LB_ALGO_NONE)
return "none";
else
@@ -2707,6 +2748,23 @@ int backend_parse_balance(const char **args, char **err, struct proxy *curproxy)
}
}
}
+ else if (strcmp(args[0], "hash") == 0) {
+ if (!*args[1]) {
+ memprintf(err, "%s requires a sample expression.", args[0]);
+ return -1;
+ }
+ curproxy->lbprm.algo &= ~BE_LB_ALGO;
+ curproxy->lbprm.algo |= BE_LB_ALGO_SMP;
+
+ ha_free(&curproxy->lbprm.arg_str);
+ curproxy->lbprm.arg_str = strdup(args[1]);
+ curproxy->lbprm.arg_len = strlen(args[1]);
+
+ if (*args[2]) {
+ memprintf(err, "%s takes no other argument (got '%s').", args[0], args[2]);
+ return -1;
+ }
+ }
else if (!strncmp(args[0], "hdr(", 4)) {
const char *beg, *end;
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 14980b36d..e14db075d 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -3294,6 +3294,47 @@ out_uri_auth_compat:
curproxy->conf.args.line = 0;
}
+ /* "balance hash" needs to compile its expression */
+ if ((curproxy->lbprm.algo & BE_LB_ALGO) == BE_LB_ALGO_SMP) {
+ int idx = 0;
+ const char *args[] = {
+ curproxy->lbprm.arg_str,
+ NULL,
+ };
+
+ err = NULL;
+ curproxy->conf.args.ctx = ARGC_USRV; // same context as use_server.
+ curproxy->lbprm.expr =
+ sample_parse_expr((char **)args, &idx,
+ curproxy->conf.file, curproxy->conf.line,
+ &err, &curproxy->conf.args, NULL);
+
+ if (!curproxy->lbprm.expr) {
+ ha_alert("%s '%s' [%s:%d]: failed to parse 'balance hash' expression '%s' in : %s.\n",
+ proxy_type_str(curproxy), curproxy->id,
+ curproxy->conf.file, curproxy->conf.line,
+ curproxy->lbprm.arg_str, err);
+ ha_free(&err);
+ cfgerr++;
+ }
+ else if (!(curproxy->lbprm.expr->fetch->val & SMP_VAL_BE_SET_SRV)) {
+ ha_alert("%s '%s' [%s:%d]: error detected while parsing 'balance hash' expression '%s' "
+ "which requires information from %s, which is not available here.\n",
+ proxy_type_str(curproxy), curproxy->id,
+ curproxy->conf.file, curproxy->conf.line,
+ curproxy->lbprm.arg_str, sample_src_names(curproxy->lbprm.expr->fetch->use));
+ cfgerr++;
+ }
+ else if (curproxy->mode == PR_MODE_HTTP && (curproxy->lbprm.expr->fetch->use & SMP_USE_L6REQ)) {
+ ha_warning("%s '%s' [%s:%d]: L6 sample fetch <%s> will be ignored in 'balance hash' expression in HTTP mode.\n",
+ proxy_type_str(curproxy), curproxy->id,
+ curproxy->conf.file, curproxy->conf.line,
+ curproxy->lbprm.arg_str);
+ }
+ else
+ curproxy->http_needed |= !!(curproxy->lbprm.expr->fetch->use & SMP_USE_HTTP_ANY);
+ }
+
/* only now we can check if some args remain unresolved.
* This must be done after the users and groups resolution.
*/
diff --git a/src/proxy.c b/src/proxy.c
index 55f4200dc..0cf3cca99 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -154,6 +154,7 @@ void free_proxy(struct proxy *p)
free(p->cookie_domain);
free(p->cookie_attrs);
free(p->lbprm.arg_str);
+ release_sample_expr(p->lbprm.expr);
free(p->server_state_file_name);
free(p->capture_name);
istfree(&p->monitor_uri);
diff --git a/src/sample.c b/src/sample.c
index 870f5dcd8..8a0a66b8c 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -1307,7 +1307,7 @@ int smp_resolve_args(struct proxy *p, char **err)
case ARGC_SRV: where = "in server directive in"; break;
case ARGC_SPOE: where = "in spoe-message directive in"; break;
case ARGC_UBK: where = "in use_backend expression in"; break;
- case ARGC_USRV: where = "in use-server expression in"; break;
+ case ARGC_USRV: where = "in use-server or balance expression in"; break;
case ARGC_HERR: where = "in http-error directive in"; break;
case ARGC_OT: where = "in ot-scope directive in"; break;
case ARGC_TCO: where = "in tcp-request connection expression in"; break;