]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] add support for RDP cookie persistence
authorEmeric Brun <ebrun@exceliance.fr>
Tue, 30 Jun 2009 15:57:00 +0000 (17:57 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 14 Jul 2009 10:50:40 +0000 (12:50 +0200)
The new statement "persist rdp-cookie" enables RDP cookie
persistence. The RDP cookie is then extracted from the RDP
protocol, and compared against available servers. If a server
matches the RDP cookie, then it gets the connection.

doc/configuration.txt
include/proto/proto_tcp.h
include/types/buffers.h
include/types/proxy.h
src/cfgparse.c
src/proto_tcp.c
src/proxy.c
src/session.c

index 3e0b7c34991ec5bcb564594cce46039f2a67a648..c9e9f76625a5569ec780f68a431add75658ad6b1 100644 (file)
@@ -712,6 +712,7 @@ option tcpka                X          X         X         X
 option tcplog               X          X         X         X
 [no] option tcpsplice       X          X         X         X
 [no] option transparent     X          -         X         X
+persist rdp-cookie          X          -         X         X
 rate-limit sessions         X          X         X         -
 redirect                    -          X         X         X
 redisp                      X          -         X         X  (deprecated)
@@ -2944,6 +2945,46 @@ no option transparent
             "transparent" option of the "bind" keyword.
 
 
+persist rdp-cookie
+persist rdp-cookie(name)
+  Enable RDP cookie-based persistence
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    no    |   yes  |   yes
+  Arguments :
+    <name>    is the optional name of the RDP cookie to check. If omitted, the
+              default cookie name "mstshash" will be used. There currently is
+              no valid reason to change this name.
+
+  This statement enables persistence based on an RDP cookie. The RDP cookie
+  contains all information required to find the server in the list of known
+  servers. So when this option is set in the backend, the request is analysed
+  and if an RDP cookie is found, it is decoded. If it matches a known server
+  which is still UP (or if "option persist" is set), then the connection is
+  forwarded to this server.
+
+  Note that this only makes sense in a TCP backend, but for this to work, the
+  frontend must have waited long enough to ensure that an RDP cookie is present
+  in the request buffer. This is the same requirement as with the "rdp-cookie"
+  load-balancing method. Thus it is higly recommended to put all statements in
+  a single "listen" section.
+
+  Example :
+        listen tse-farm
+            bind :3389
+            # wait up to 5s for an RDP cookie in the request
+            tcp-request inspect-delay 5s
+            tcp-request content accept if RDP_COOKIE
+            # apply RDP cookie persistence
+            persist rdp-cookie
+            # if server is unknown, let's balance on the same cookie.
+           # alternatively, "balance leastconn" may be useful too.
+            balance rdp-cookie
+            server srv1 1.1.1.1:3389
+            server srv2 1.1.1.2:3389
+
+  See also : "balance rdp-cookie", "tcp-request" and the "req_rdp_cookie" ACL.
+
+
 rate-limit sessions <rate>
   Set a limit on the number of new sessions accepted per second on a frontend
   May be used in sections :   defaults | frontend | listen | backend
index c188c27badd4869f0b97c13e8bcf5c19057a3f17..2cf3be18c1f328ca063a1c4c5ba6e606763f0611 100644 (file)
@@ -35,6 +35,7 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen);
 int tcp_inspect_request(struct session *s, struct buffer *req, int an_bit);
 int acl_fetch_rdp_cookie(struct proxy *px, struct session *l4, void *l7, int dir,
                          struct acl_expr *expr, struct acl_test *test);
+int tcp_persist_rdp_cookie(struct session *s, struct buffer *req, int an_bit);
 
 #endif /* _PROTO_PROTO_TCP_H */
 
index 234020f7efc3f7c7e517c335be144c8f1edc8a34..055311663fd91b4460b7a2beda6bf0031ad889b5 100644 (file)
 #define AN_REQ_UNIX_STATS       0x00000100  /* process unix stats socket request */
 
 #define AN_RTR_HTTP_HDR         0x00000200  /* inspect HTTP response headers */
+#define AN_REQ_PRST_RDP_COOKIE  0x00000400  /* persistence on rdp cookie */
 
 /* describes a chunk of string */
 struct chunk {
index 41dd3e18baa46478a07009f29a6076478b04dca8..43f31a6e066780adc9c5da78ce63b3fa5e964349 100644 (file)
 #define PR_O2_LOGERRORS        0x00000040      /* log errors and retries at level LOG_ERR */
 #define PR_O2_SMARTACC         0x00000080      /* don't immediately ACK request after accept */
 #define PR_O2_SMARTCON         0x00000100      /* don't immediately send empty ACK after connect */
+#define PR_O2_RDPC_PRST        0x00000200      /* Actvate rdp cookie analyser */
 
 /* This structure is used to apply fast weighted round robin on a server group */
 struct fwrr_group {
@@ -197,6 +198,8 @@ struct proxy {
        char *cookie_domain;                    /* domain used to insert the cookie */
        char *cookie_name;                      /* name of the cookie to look for */
        int  cookie_len;                        /* strlen(cookie_name), computed only once */
+       char *rdp_cookie_name;                  /* name of the RDP cookie to look for */
+       int  rdp_cookie_len;                    /* strlen(rdp_cookie_name), computed only once */
        char *url_param_name;                   /* name of the URL parameter used for hashing */
        int  url_param_len;                     /* strlen(url_param_name), computed only once */
        unsigned url_param_post_limit;          /* if checking POST body for URI parameter, max body to wait for */
index 73446a43218349f7dcc41a77fa66e7c3034d2f8c..114d8766f0fa09a8ae02769a08d6bbcc44e0b48e 100644 (file)
@@ -824,6 +824,10 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                                curproxy->cookie_name = strdup(defproxy.cookie_name);
                        curproxy->cookie_len = defproxy.cookie_len;
 
+                       if (defproxy.rdp_cookie_name)
+                                curproxy->rdp_cookie_name = strdup(defproxy.rdp_cookie_name);
+                       curproxy->rdp_cookie_len = defproxy.rdp_cookie_len;
+
                        if (defproxy.url_param_name)
                                curproxy->url_param_name = strdup(defproxy.url_param_name);
                        curproxy->url_param_len = defproxy.url_param_len;
@@ -891,6 +895,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                 */
                free(defproxy.check_req);
                free(defproxy.cookie_name);
+               free(defproxy.rdp_cookie_name);
                free(defproxy.url_param_name);
                free(defproxy.hh_name);
                free(defproxy.capture_name);
@@ -1222,6 +1227,49 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                        return -1;
                }
        }/* end else if (!strcmp(args[0], "cookie"))  */
+       else if (!strcmp(args[0], "persist")) {  /* persist */
+               if (*(args[1]) == 0) {
+                       Alert("parsing [%s:%d] : missing persist method.\n",
+                               file, linenum);
+                        return -1;
+                }
+
+               if (!strncmp(args[1], "rdp-cookie", 10)) {
+                       curproxy->options2 |= PR_O2_RDPC_PRST;
+
+                       if (*(args[1] + 10 ) == '(') { /* cookie name */
+                               const char *beg, *end;
+
+                               beg = args[1] + 11;
+                               end = strchr(beg, ')');
+
+                               if (!end || end == beg) {
+                                       Alert("parsing [%s:%d] : persist rdp-cookie(name)' requires an rdp cookie name.\n",
+                                             file, linenum);
+                                       return -1;
+                               }
+
+                               free(curproxy->rdp_cookie_name);
+                               curproxy->rdp_cookie_name = my_strndup(beg, end - beg);
+                               curproxy->rdp_cookie_len = end-beg;
+                       }
+                       else if (*(args[1] + 10 ) == '\0') { /* default cookie name 'msts' */
+                               free(curproxy->rdp_cookie_name);
+                               curproxy->rdp_cookie_name = strdup("msts");
+                               curproxy->rdp_cookie_len = strlen(curproxy->rdp_cookie_name);
+                       }
+                       else { /* syntax */
+                               Alert("parsing [%s:%d] : persist rdp-cookie(name)' requires an rdp cookie name.\n",
+                                     file, linenum);
+                               return -1;
+                       }
+               }
+               else {
+                       Alert("parsing [%s:%d] : unknown persist method.\n",
+                             file, linenum);
+                       return -1;
+               }
+       }
        else if (!strcmp(args[0], "appsession")) {  /* cookie name */
 
                if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
index 93fc126e113f1f89b6bbbaa68445903831fc21fe..ff32726cd94813d69018d6934056bf6ba1a78923 100644 (file)
@@ -446,6 +446,74 @@ int tcp_inspect_request(struct session *s, struct buffer *req, int an_bit)
        return 1;
 }
 
+/* Apply RDP cookie persistence to the current session. For this, the function
+ * tries to extract an RDP cookie from the request buffer, and look for the
+ * matching server in the list. If the server is found, it is assigned to the
+ * session. This always returns 1, and the analyser removes itself from the
+ * list. Nothing is performed if a server was already assigned.
+ */
+int tcp_persist_rdp_cookie(struct session *s, struct buffer *req, int an_bit)
+{
+       struct proxy    *px   = s->be;
+       int              ret;
+       struct acl_expr  expr;
+       struct acl_test  test;
+       struct server *srv = px->srv;
+       struct sockaddr_in addr;
+       char *p;
+
+       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)
+               goto no_cookie;
+
+       memset(&expr, 0, sizeof(expr));
+       memset(&test, 0, sizeof(test));
+
+       expr.arg.str = s->be->rdp_cookie_name;
+       expr.arg_len = s->be->rdp_cookie_len;
+
+       ret = acl_fetch_rdp_cookie(px, s, NULL, ACL_DIR_REQ, &expr, &test);
+       if (ret == 0 || (test.flags & ACL_TEST_F_MAY_CHANGE) || test.len == 0)
+               goto no_cookie;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sin_family = AF_INET;
+
+       /* Considering an rdp cookie detected using acl, test.ptr ended with <cr><lf> and should return */
+       addr.sin_addr.s_addr = strtoul(test.ptr, &p, 10);
+       if (*p != '.')
+               goto no_cookie;
+       p++;
+       addr.sin_port = (unsigned short)strtoul(p, &p, 10);
+       if (*p != '.')
+               goto no_cookie;
+
+       while (srv) {
+               if (memcmp(&addr, &(srv->addr), sizeof(addr)) == 0) {
+                       if ((srv->state & SRV_RUNNING) || (px->options & PR_O_PERSIST)) {
+                               /* we found the server and it is usable */
+                               s->flags |= SN_DIRECT | SN_ASSIGNED;
+                               s->srv = srv;
+                               break;
+                       }
+               }
+               srv = srv->next;
+       }
+
+no_cookie:
+       req->analysers &= ~an_bit;
+       req->analyse_exp = TICK_ETERNITY;
+       return 1;
+}
+
 
 /* This function should be called to parse a line starting with the "tcp-request"
  * keyword.
index f6c142f9b00dd2c8f69b8b4b31eb6fd684b025d3..4e75b59b40615675ebf5c3e6e98de30f8978a838 100644 (file)
@@ -677,6 +677,12 @@ int session_set_backend(struct session *s, struct proxy *be)
                s->req->analysers |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_PROCESS_BE | AN_REQ_HTTP_INNER;
        }
 
+       /* If the backend does requires RDP cookie persistence, we have to
+        * enable the corresponding analyser.
+        */
+       if (s->be->options2 & PR_O2_RDPC_PRST)
+               s->req->analysers |= AN_REQ_PRST_RDP_COOKIE;
+
        return 1;
 }
 
index af6061f59822ea84e4abbcc3e5a7f20942d898a6..d116ef216ccec1f96fba1e70ab24763b548c4081 100644 (file)
@@ -850,6 +850,12 @@ resync_stream_interface:
                                        if (!http_process_request_body(s, s->req, AN_REQ_HTTP_BODY))
                                                break;
                                }
+
+                               if (s->req->analysers & AN_REQ_PRST_RDP_COOKIE) {
+                                       last_ana |= AN_REQ_PRST_RDP_COOKIE;
+                                       if (!tcp_persist_rdp_cookie(s, s->req, AN_REQ_PRST_RDP_COOKIE))
+                                               break;
+                               }
                        }
                }