From: Willy Tarreau Date: Thu, 5 Apr 2012 19:09:48 +0000 (+0200) Subject: MEDIUM: session: implement the "use-server" directive X-Git-Tag: v1.5-dev9~117 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4a5cadea400f9d6503a62f24399cb70292b8b5d7;p=thirdparty%2Fhaproxy.git 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 2ede208605..337cfad8a2 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 unless See also: "default_backend", "tcp-request", and section 7 about ACLs. +use-server if +use-server unless + 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 : + is the name of a valid server in the same backend section. + + 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 42e2a5633b..6280c4f34a 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 26cdfe4650..d69a91416c 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -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 */ diff --git a/src/cfgparse.c b/src/cfgparse.c index 5996e26728..d827d4c0fb 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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; diff --git a/src/haproxy.c b/src/haproxy.c index 2858517724..c0b7d51e1c 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -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); diff --git a/src/proto_http.c b/src/proto_http.c index 3f6ec0484f..9af3f690b7 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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) { diff --git a/src/proxy.c b/src/proxy.c index 84d430aba9..7f15bb2654 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -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); diff --git a/src/session.c b/src/session.c index e5b76eb3a2..b90b25465f 100644 --- a/src/session.c +++ b/src/session.c @@ -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;