From: Emeric Brun Date: Tue, 30 Jun 2009 15:57:00 +0000 (+0200) Subject: [MEDIUM] add support for RDP cookie persistence X-Git-Tag: v1.4-dev1~21 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=647caf1ebcec53acb52a60eaaaf6d47e1f6469d6;p=thirdparty%2Fhaproxy.git [MEDIUM] add support for RDP cookie persistence 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. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 3e0b7c3499..c9e9f76625 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 : + 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 Set a limit on the number of new sessions accepted per second on a frontend May be used in sections : defaults | frontend | listen | backend diff --git a/include/proto/proto_tcp.h b/include/proto/proto_tcp.h index c188c27bad..2cf3be18c1 100644 --- a/include/proto/proto_tcp.h +++ b/include/proto/proto_tcp.h @@ -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 */ diff --git a/include/types/buffers.h b/include/types/buffers.h index 234020f7ef..055311663f 100644 --- a/include/types/buffers.h +++ b/include/types/buffers.h @@ -116,6 +116,7 @@ #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 { diff --git a/include/types/proxy.h b/include/types/proxy.h index 41dd3e18ba..43f31a6e06 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -119,6 +119,7 @@ #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 */ diff --git a/src/cfgparse.c b/src/cfgparse.c index 73446a4321..114d8766f0 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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)) diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 93fc126e11..ff32726cd9 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -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 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. diff --git a/src/proxy.c b/src/proxy.c index f6c142f9b0..4e75b59b40 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -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; } diff --git a/src/session.c b/src/session.c index af6061f598..d116ef216c 100644 --- a/src/session.c +++ b/src/session.c @@ -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; + } } }