]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: session: implement the "use-server" directive
authorWilly Tarreau <w@1wt.eu>
Thu, 5 Apr 2012 19:09:48 +0000 (21:09 +0200)
committerWilly Tarreau <w@1wt.eu>
Thu, 5 Apr 2012 19:14:10 +0000 (21:14 +0200)
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.

doc/configuration.txt
include/types/buffers.h
include/types/proxy.h
src/cfgparse.c
src/haproxy.c
src/proto_http.c
src/proxy.c
src/session.c

index 2ede20860576f17e2ee91ff26f409876ec32729d..337cfad8a29bc00c4aa1bef619940d3e4d180f6d 100644 (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
 ------------------------------------
 
index 42e2a5633b6b6ef50b144cb08827d4a5496b9ca2..6280c4f34aab5a17df07b692f4cecda254d3b73a 100644 (file)
 #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 */
index 26cdfe4650927f9aca394e4d8071e802e87440db..d69a91416c6c19d512d6d43e7b43b77e1a7575a4 100644 (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 */
index 5996e26728e8d1b0f3ba8085813a8dcd9f5cc24b..d827d4c0fbb7a4168ea6feaf72c084cf3c1e8e36 100644 (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;
index 285851772470b120a16736cb6fa4f507d265f0e3..c0b7d51e1c2e603547a4f2c1c5f06f8d127b1ed4 100644 (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);
index 3f6ec0484f35ac4999f637affaae36ba7bd7133c..9af3f690b7f0ded8ef82076870388a370a2f396b 100644 (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) {
index 84d430aba9fbc518d7f35eb82b537af27a60ced3..7f15bb26540b7d1dd0d8e574f655cecd9d5c4c73 100644 (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);
index e5b76eb3a222441b9c59419cdd7b4836f4d7e869..b90b25465fed343437f59db59dd92a0e1228b579 100644 (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;