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.
This commit is contained in:
Willy Tarreau 2012-04-05 21:09:48 +02:00
parent ceafb4aa92
commit 4a5cadea40
8 changed files with 193 additions and 7 deletions

View File

@ -1118,6 +1118,7 @@ timeout srvtimeout (deprecated) X - X X
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 @@ use_backend <backend> unless <condition>
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
------------------------------------

View File

@ -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 */

View File

@ -205,6 +205,7 @@ struct proxy {
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 @@ struct switching_rule {
} 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 */

View File

@ -2702,6 +2702,47 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
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 @@ int check_config_validity()
while (curproxy != NULL) {
struct switching_rule *rule;
struct server_rule *srule;
struct sticking_rule *mrule;
struct tcp_rule *trule;
struct listener *listener;
@ -5794,6 +5836,20 @@ int check_config_validity()
}
}
/* 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 */
list_for_each_entry(mrule, &curproxy->sticking_rules, list) {
struct proxy *target;

View File

@ -797,6 +797,7 @@ void deinit(void)
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 @@ void deinit(void)
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);

View File

@ -6221,7 +6221,7 @@ void manage_client_side_cookies(struct session *t, struct buffer *req)
* 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) {

View File

@ -427,6 +427,7 @@ void init_new_proxy(struct proxy *p)
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);

View File

@ -1041,6 +1041,55 @@ static int process_switching_rules(struct session *s, struct buffer *req, int an
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 @@ struct task *process_session(struct task *t)
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;