MEDIUM: session: implement the "use-server" directive
Sometimes it is desirable to forward a particular request to a specific
server without having to declare a dedicated backend for this server. This
can be achieved using the "use-server" rules. These rules are evaluated after
the "redirect" rules and before evaluating cookies, and they have precedence
on them. There may be as many "use-server" rules as desired. All of these
rules are evaluated in their declaration order, and the first one which
matches will assign the server.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 2ede208..337cfad 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1118,6 +1118,7 @@
timeout tarpit X X X X
transparent (deprecated) X - X X
use_backend - X X -
+use-server - - X X
------------------------------------+----------+----------+---------+---------
keyword defaults frontend listen backend
@@ -6597,6 +6598,60 @@
See also: "default_backend", "tcp-request", and section 7 about ACLs.
+use-server <server> if <condition>
+use-server <server> unless <condition>
+ Only use a specific server if/unless an ACL-based condition is matched.
+ May be used in sections : defaults | frontend | listen | backend
+ no | no | yes | yes
+ Arguments :
+ <server> is the name of a valid server in the same backend section.
+
+ <condition> is a condition composed of ACLs, as described in section 7.
+
+ By default, connections which arrive to a backend are load-balanced across
+ the available servers according to the configured algorithm, unless a
+ persistence mechanism such as a cookie is used and found in the request.
+
+ Sometimes it is desirable to forward a particular request to a specific
+ server without having to declare a dedicated backend for this server. This
+ can be achieved using the "use-server" rules. These rules are evaluated after
+ the "redirect" rules and before evaluating cookies, and they have precedence
+ on them. There may be as many "use-server" rules as desired. All of these
+ rules are evaluated in their declaration order, and the first one which
+ matches will assign the server.
+
+ If a rule designates a server which is down, and "option persist" is not used
+ and no force-persist rule was validated, it is ignored and evaluation goes on
+ with the next rules until one matches.
+
+ In the first form, the server will be used if the condition is met. In the
+ second form, the server will be used if the condition is not met. If no
+ condition is valid, the processing continues and the server will be assigned
+ according to other persistence mechanisms.
+
+ Note that even if a rule is matched, cookie processing is still performed but
+ does not assign the server. This allows prefixed cookies to have their prefix
+ stripped.
+
+ The "use-server" statement works both in HTTP and TCP mode. This makes it
+ suitable for use with content-based inspection. For instance, a server could
+ be selected in a farm according to the TLS SNI field. And if these servers
+ have their weight set to zero, they will not be used for other traffic.
+
+ Example :
+ # intercept incoming TLS requests based on the SNI field
+ use-server www if { req_ssl_sni -i www.example.com }
+ server www 192.168.0.1:443 weight 0
+ use-server mail if { req_ssl_sni -i mail.example.com }
+ server mail 192.168.0.1:587 weight 0
+ use-server imap if { req_ssl_sni -i imap.example.com }
+ server mail 192.168.0.1:993 weight 0
+ # all the rest is forwarded to this server
+ server default 192.168.0.2:443 check
+
+ See also: "use_backend", serction 5 about server and section 7 about ACLs.
+
+
5. Server and default-server options
------------------------------------
diff --git a/include/types/buffers.h b/include/types/buffers.h
index 42e2a56..6280c4f 100644
--- a/include/types/buffers.h
+++ b/include/types/buffers.h
@@ -144,12 +144,13 @@
#define AN_REQ_SWITCHING_RULES 0x00000010 /* apply the switching rules */
#define AN_REQ_INSPECT_BE 0x00000020 /* inspect request contents in the backend */
#define AN_REQ_HTTP_PROCESS_BE 0x00000040 /* process the backend's HTTP part */
-#define AN_REQ_HTTP_INNER 0x00000080 /* inner processing of HTTP request */
-#define AN_REQ_HTTP_TARPIT 0x00000100 /* wait for end of HTTP tarpit */
-#define AN_REQ_HTTP_BODY 0x00000200 /* inspect HTTP request body */
-#define AN_REQ_STICKING_RULES 0x00000400 /* table persistence matching */
-#define AN_REQ_PRST_RDP_COOKIE 0x00000800 /* persistence on rdp cookie */
-#define AN_REQ_HTTP_XFER_BODY 0x00001000 /* forward request body */
+#define AN_REQ_SRV_RULES 0x00000080 /* use-server rules */
+#define AN_REQ_HTTP_INNER 0x00000100 /* inner processing of HTTP request */
+#define AN_REQ_HTTP_TARPIT 0x00000200 /* wait for end of HTTP tarpit */
+#define AN_REQ_HTTP_BODY 0x00000400 /* inspect HTTP request body */
+#define AN_REQ_STICKING_RULES 0x00000800 /* table persistence matching */
+#define AN_REQ_PRST_RDP_COOKIE 0x00001000 /* persistence on rdp cookie */
+#define AN_REQ_HTTP_XFER_BODY 0x00002000 /* forward request body */
/* response analysers */
#define AN_RES_INSPECT 0x00010000 /* content inspection */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 26cdfe4..d69a914 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -205,6 +205,7 @@
struct list persist_rules; /* 'force-persist' and 'ignore-persist' rules (chained) */
struct list sticking_rules; /* content sticking rules (chained) */
struct list storersp_rules; /* content store response rules (chained) */
+ struct list server_rules; /* server switching rules (chained) */
struct { /* TCP request processing */
unsigned int inspect_delay; /* inspection delay */
struct list inspect_rules; /* inspection rules */
@@ -350,6 +351,15 @@
} be;
};
+struct server_rule {
+ struct list list; /* list linked to from the proxy */
+ struct acl_cond *cond; /* acl condition to meet */
+ union {
+ struct server *ptr; /* target server */
+ char *name; /* target server name during config parsing */
+ } srv;
+};
+
struct persist_rule {
struct list list; /* list linked to from the proxy */
struct acl_cond *cond; /* acl condition to meet */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 5996e26..d827d4c 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2702,6 +2702,47 @@
LIST_INIT(&rule->list);
LIST_ADDQ(&curproxy->switching_rules, &rule->list);
}
+ else if (strcmp(args[0], "use-server") == 0) {
+ struct server_rule *rule;
+
+ if (curproxy == &defproxy) {
+ Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
+ err_code |= ERR_WARN;
+
+ if (*(args[1]) == 0) {
+ Alert("parsing [%s:%d] : '%s' expects a server name.\n", file, linenum, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ if (strcmp(args[2], "if") != 0 && strcmp(args[2], "unless") != 0) {
+ Alert("parsing [%s:%d] : '%s' requires either 'if' or 'unless' followed by a condition.\n",
+ file, linenum, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 2)) == NULL) {
+ Alert("parsing [%s:%d] : error detected while parsing switching rule.\n",
+ file, linenum);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ err_code |= warnif_cond_requires_resp(cond, file, linenum);
+
+ rule = (struct server_rule *)calloc(1, sizeof(*rule));
+ rule->cond = cond;
+ rule->srv.name = strdup(args[1]);
+ LIST_INIT(&rule->list);
+ LIST_ADDQ(&curproxy->server_rules, &rule->list);
+ curproxy->be_req_ana |= AN_REQ_SRV_RULES;
+ }
else if ((!strcmp(args[0], "force-persist")) ||
(!strcmp(args[0], "ignore-persist"))) {
struct persist_rule *rule;
@@ -5603,6 +5644,7 @@
while (curproxy != NULL) {
struct switching_rule *rule;
+ struct server_rule *srule;
struct sticking_rule *mrule;
struct tcp_rule *trule;
struct listener *listener;
@@ -5792,6 +5834,20 @@
target->bind_proc = curproxy->bind_proc ?
(target->bind_proc | curproxy->bind_proc) : 0;
}
+ }
+
+ /* find the target proxy for 'use_backend' rules */
+ list_for_each_entry(srule, &curproxy->server_rules, list) {
+ struct server *target = findserver(curproxy, srule->srv.name);
+
+ if (!target) {
+ Alert("config : %s '%s' : unable to find server '%s' referenced in a 'use-server' rule.\n",
+ proxy_type_str(curproxy), curproxy->id, srule->srv.name);
+ cfgerr++;
+ continue;
+ }
+ free((void *)srule->srv.name);
+ srule->srv.ptr = target;
}
/* find the target table for 'stick' rules */
diff --git a/src/haproxy.c b/src/haproxy.c
index 2858517..c0b7d51 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -797,6 +797,7 @@
struct hdr_exp *exp, *expb;
struct acl *acl, *aclb;
struct switching_rule *rule, *ruleb;
+ struct server_rule *srule, *sruleb;
struct redirect_rule *rdr, *rdrb;
struct wordlist *wl, *wlb;
struct cond_wordlist *cwl, *cwlb;
@@ -891,6 +892,13 @@
free(acl);
}
+ list_for_each_entry_safe(srule, sruleb, &p->server_rules, list) {
+ LIST_DEL(&srule->list);
+ prune_acl_cond(srule->cond);
+ free(srule->cond);
+ free(srule);
+ }
+
list_for_each_entry_safe(rule, ruleb, &p->switching_rules, list) {
LIST_DEL(&rule->list);
prune_acl_cond(rule->cond);
diff --git a/src/proto_http.c b/src/proto_http.c
index 3f6ec04..9af3f69 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -6221,7 +6221,7 @@
* empty cookies and mark them as invalid.
* The same behaviour is applied when persistence must be ignored.
*/
- if ((delim == val_beg) || (t->flags & SN_IGNORE_PRST))
+ if ((delim == val_beg) || (t->flags & (SN_IGNORE_PRST | SN_ASSIGNED)))
srv = NULL;
while (srv) {
diff --git a/src/proxy.c b/src/proxy.c
index 84d430a..7f15bb2 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -427,6 +427,7 @@
LIST_INIT(&p->redirect_rules);
LIST_INIT(&p->mon_fail_cond);
LIST_INIT(&p->switching_rules);
+ LIST_INIT(&p->server_rules);
LIST_INIT(&p->persist_rules);
LIST_INIT(&p->sticking_rules);
LIST_INIT(&p->storersp_rules);
diff --git a/src/session.c b/src/session.c
index e5b76eb..b90b254 100644
--- a/src/session.c
+++ b/src/session.c
@@ -1041,6 +1041,55 @@
return 0;
}
+/* This stream analyser works on a request. It applies all use-server rules on
+ * it then returns 1. The data must already be present in the buffer otherwise
+ * they won't match. It always returns 1.
+ */
+static int process_server_rules(struct session *s, struct buffer *req, int an_bit)
+{
+ struct proxy *px = s->be;
+ struct server_rule *rule;
+
+ DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n",
+ now_ms, __FUNCTION__,
+ s,
+ req,
+ req->rex, req->wex,
+ req->flags,
+ req->l,
+ req->analysers);
+
+ if (!(s->flags & SN_ASSIGNED)) {
+ list_for_each_entry(rule, &px->server_rules, list) {
+ int ret;
+
+ ret = acl_exec_cond(rule->cond, s->be, s, &s->txn, ACL_DIR_REQ);
+ ret = acl_pass(ret);
+ if (rule->cond->pol == ACL_COND_UNLESS)
+ ret = !ret;
+
+ if (ret) {
+ struct server *srv = rule->srv.ptr;
+
+ if ((srv->state & SRV_RUNNING) ||
+ (px->options & PR_O_PERSIST) ||
+ (s->flags & SN_FORCE_PRST)) {
+ s->flags |= SN_DIRECT | SN_ASSIGNED;
+ set_target_server(&s->target, srv);
+ break;
+ }
+ /* if the server is not UP, let's go on with next rules
+ * just in case another one is suited.
+ */
+ }
+ }
+ }
+
+ req->analysers &= ~an_bit;
+ req->analyse_exp = TICK_ETERNITY;
+ return 1;
+}
+
/* This stream analyser works on a request. It applies all sticking rules on
* it then returns 1. The data must already be present in the buffer otherwise
* they won't match. It always returns 1.
@@ -1526,6 +1575,12 @@
UPDATE_ANALYSERS(s->req->analysers, ana_list, ana_back, AN_REQ_HTTP_TARPIT);
}
+ if (ana_list & AN_REQ_SRV_RULES) {
+ if (!process_server_rules(s, s->req, AN_REQ_SRV_RULES))
+ break;
+ UPDATE_ANALYSERS(s->req->analysers, ana_list, ana_back, AN_REQ_SRV_RULES);
+ }
+
if (ana_list & AN_REQ_HTTP_INNER) {
if (!http_process_request(s, s->req, AN_REQ_HTTP_INNER))
break;