diff --git a/doc/configuration.txt b/doc/configuration.txt index fae5027e1..47842d6c7 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -861,7 +861,8 @@ acl [flags] [operator] ... See section 7 about ACL usage. -appsession len timeout [request-learn] +appsession len timeout + [request-learn] [prefix] [mode ] Define session stickiness on an existing application cookie. May be used in sections : defaults | frontend | listen | backend no | no | yes | yes @@ -869,7 +870,7 @@ appsession len timeout [request-learn] this is the name of the cookie used by the application and which HAProxy will have to learn for each new session. - this is the number of characters that will be memorized and + this is the max number of characters that will be memorized and checked in each cookie value. this is the time after which the cookie will be removed from @@ -884,13 +885,34 @@ appsession len timeout [request-learn] the application's session and the correct server is selected. It is recommended to specify this option to improve reliability. + prefix When this option is specified, haproxy will match on the cookie + prefix (or URL parameter prefix). The appsession value is the + data following this prefix. + + Example : + appsession ASPSESSIONID len 64 timeout 3h prefix + + This will match the cookie ASPSESSIONIDXXXX=XXXXX, + the appsession value will be XXXX=XXXXX. + + mode This option allows to change the URL parser mode. + 2 modes are currently supported : + - path-parameters : + The parser looks for the appsession in the path parameters + part (each parameter is separated by a semi-colon), which is + convenient for JSESSIONID for example. + This is the default mode if the option is not set. + - query-string : + In this mode, the parser will look for the appsession in the + query string. + When an application cookie is defined in a backend, HAProxy will check when the server sets such a cookie, and will store its value in a table, and associate it with the server's identifier. Up to characters from the value will be retained. On each connection, haproxy will look for this - cookie both in the "Cookie:" headers, and as a URL parameter in the query - string. If a known value is found, the client will be directed to the server - associated with this value. Otherwise, the load balancing algorithm is + cookie both in the "Cookie:" headers, and as a URL parameter (depending on + the mode used). If a known value is found, the client will be directed to the + server associated with this value. Otherwise, the load balancing algorithm is applied. Cookies are automatically removed from memory when they have been unused for a duration longer than . diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 34fccdf2d..d5a7306bc 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -75,7 +75,7 @@ int apply_filter_to_req_headers(struct session *t, struct buffer *req, struct hd int apply_filter_to_req_line(struct session *t, struct buffer *req, struct hdr_exp *exp); int apply_filters_to_request(struct session *t, struct buffer *req, struct hdr_exp *exp); int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_exp *exp); -void manage_client_side_appsession(struct session *t, const char *buf); +void manage_client_side_appsession(struct session *t, const char *buf, int len); void manage_client_side_cookies(struct session *t, struct buffer *req); void manage_server_side_cookies(struct session *t, struct buffer *rtr); void check_response_for_cacheability(struct session *t, struct buffer *rtr); diff --git a/include/types/proxy.h b/include/types/proxy.h index 8348f92ed..92517ca2e 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -123,7 +123,15 @@ #define PR_O2_LOGHCHKS 0x00000800 /* log health checks */ #define PR_O2_INDEPSTR 0x00001000 /* independant streams, don't update rex on write */ #define PR_O2_SOCKSTAT 0x00002000 /* collect & provide separate statistics for sockets */ -#define PR_O2_AS_REQL 0x00004000 /* appsession: learn the session id from the request */ + +/* appsession */ +#define PR_O2_AS_REQL 0x00004000 /* learn the session id from the request */ +#define PR_O2_AS_PFX 0x00008000 /* match on the cookie prefix */ + +/* Encoding of appsession cookie matching modes : 2 possible values => 1 bit */ +#define PR_O2_AS_M_PP 0x00000000 /* path-parameters mode (the default mode) */ +#define PR_O2_AS_M_QS 0x00010000 /* query-string mode */ +#define PR_O2_AS_M_ANY 0x00010000 /* mask covering all PR_O2_AS_M_* values */ struct error_snapshot { struct timeval when; /* date of this event, (tv_sec == 0) means "never" */ diff --git a/src/cfgparse.c b/src/cfgparse.c index 34d4476e2..3df0a21b0 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -1541,7 +1541,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) err_code |= ERR_WARN; if (*(args[5]) == 0) { - Alert("parsing [%s:%d] : '%s' expects 'appsession' 'len' 'timeout' .\n", + Alert("parsing [%s:%d] : '%s' expects 'appsession' 'len' 'timeout' [options*].\n", file, linenum, args[0]); err_code |= ERR_ALERT | ERR_FATAL; goto out; @@ -1568,9 +1568,34 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) cur_arg = 6; curproxy->options2 &= ~PR_O2_AS_REQL; + curproxy->options2 &= ~PR_O2_AS_M_ANY; + curproxy->options2 |= PR_O2_AS_M_PP; while (*(args[cur_arg])) { - if (!strcmp(args[cur_arg], "request-learn")) + if (!strcmp(args[cur_arg], "request-learn")) { curproxy->options2 |= PR_O2_AS_REQL; + } else if (!strcmp(args[cur_arg], "prefix")) { + curproxy->options2 |= PR_O2_AS_PFX; + } else if (!strcmp(args[cur_arg], "mode")) { + if (!*args[cur_arg + 1]) { + Alert("parsing [%s:%d] : '%s': missing argument for '%s'.\n", + file, linenum, args[0], args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + cur_arg++; + if (!strcmp(args[cur_arg], "query-string")) { + curproxy->options2 &= ~PR_O2_AS_M_ANY; + curproxy->options2 |= PR_O2_AS_M_QS; + } else if (!strcmp(args[cur_arg], "path-parameters")) { + curproxy->options2 &= ~PR_O2_AS_M_ANY; + curproxy->options2 |= PR_O2_AS_M_PP; + } else { + Alert("parsing [%s:%d] : unknown mode '%s'\n", file, linenum, args[cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } cur_arg++; } } /* Url App Session */ diff --git a/src/proto_http.c b/src/proto_http.c index f896fbbdf..cbaae5b35 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -2479,17 +2479,7 @@ int http_process_request(struct session *s, struct buffer *req, int an_bit) } /* - * 7: the appsession cookie was looked up very early in 1.2, - * so let's do the same now. - */ - - /* It needs to look into the URI */ - if (s->be->appsession_name) { - get_srv_from_appsession(s, &req->data[msg->som], msg->sl.rq.l); - } - - /* - * 8: Now we can work with the cookies. + * 7: Now we can work with the cookies. * Note that doing so might move headers in the request, but * the fields will stay coherent and the URI will not move. * This should only be performed in the backend. @@ -2498,6 +2488,16 @@ int http_process_request(struct session *s, struct buffer *req, int an_bit) && !(txn->flags & (TX_CLDENY|TX_CLTARPIT))) manage_client_side_cookies(s, req); + /* + * 8: the appsession cookie was looked up very early in 1.2, + * so let's do the same now. + */ + + /* It needs to look into the URI */ + if ((s->sessid == NULL) && s->be->appsession_name) { + get_srv_from_appsession(s, &req->data[msg->som + msg->sl.rq.u], msg->sl.rq.u_l); + } + /* * 9: add X-Forwarded-For if either the frontend or the backend * asks for it. @@ -3783,11 +3783,15 @@ int apply_filters_to_request(struct session *t, struct buffer *req, struct hdr_e * Try to retrieve the server associated to the appsession. * If the server is found, it's assigned to the session. */ -void manage_client_side_appsession(struct session *t, const char *buf) { +void manage_client_side_appsession(struct session *t, const char *buf, int len) { struct http_txn *txn = &t->txn; appsess *asession = NULL; char *sessid_temp = NULL; + if (len > t->be->appsession_len) { + len = t->be->appsession_len; + } + if (t->be->options2 & PR_O2_AS_REQL) { /* request-learn option is enabled : store the sessid in the session for future use */ if (t->sessid != NULL) { @@ -3801,8 +3805,8 @@ void manage_client_side_appsession(struct session *t, const char *buf) { return; } - memcpy(t->sessid, buf, t->be->appsession_len); - t->sessid[t->be->appsession_len] = 0; + memcpy(t->sessid, buf, len); + t->sessid[len] = 0; } if ((sessid_temp = pool_alloc2(apools.sessid)) == NULL) { @@ -3811,8 +3815,8 @@ void manage_client_side_appsession(struct session *t, const char *buf) { return; } - memcpy(sessid_temp, buf, t->be->appsession_len); - sessid_temp[t->be->appsession_len] = 0; + memcpy(sessid_temp, buf, len); + sessid_temp[len] = 0; asession = appsession_hash_lookup(&(t->be->htbl_proxy), sessid_temp); /* free previously allocated memory */ @@ -4080,12 +4084,25 @@ void manage_client_side_cookies(struct session *t, struct buffer *req) } } - if ((t->be->appsession_name != NULL) && - (memcmp(p1, t->be->appsession_name, p2 - p1) == 0)) { - /* first, let's see if the cookie is our appcookie*/ + if (t->be->appsession_name != NULL) { + int cmp_len, value_len; + char *value_begin; - /* Cool... it's the right one */ - manage_client_side_appsession(t, p3); + if (t->be->options2 & PR_O2_AS_PFX) { + cmp_len = MIN(p4 - p1, t->be->appsession_name_len); + value_begin = p1 + t->be->appsession_name_len; + value_len = p4 - p1 - t->be->appsession_name_len; + } else { + cmp_len = p2 - p1; + value_begin = p3; + value_len = p4 - p3; + } + + /* let's see if the cookie is our appcookie */ + if (memcmp(p1, t->be->appsession_name, cmp_len) == 0) { + /* Cool... it's the right one */ + manage_client_side_appsession(t, value_begin, value_len); + } #if defined(DEBUG_HASH) Alert("manage_client_side_cookies\n"); appsession_hash_dump(&(t->be->htbl_proxy)); @@ -4497,24 +4514,36 @@ void manage_server_side_cookies(struct session *t, struct buffer *rtr) } } /* next, let's see if the cookie is our appcookie */ - else if ((t->be->appsession_name != NULL) && - (memcmp(p1, t->be->appsession_name, p2 - p1) == 0)) { + else if (t->be->appsession_name != NULL) { + int cmp_len, value_len; + char *value_begin; - /* Cool... it's the right one */ + if (t->be->options2 & PR_O2_AS_PFX) { + cmp_len = MIN(p4 - p1, t->be->appsession_name_len); + value_begin = p1 + t->be->appsession_name_len; + value_len = MIN(t->be->appsession_len, p4 - p1 - t->be->appsession_name_len); + } else { + cmp_len = p2 - p1; + value_begin = p3; + value_len = MIN(t->be->appsession_len, p4 - p3); + } - if (t->sessid != NULL) { - /* free previously allocated memory as we don't need it anymore */ - pool_free2(apools.sessid, t->sessid); + if (memcmp(p1, t->be->appsession_name, cmp_len) == 0) { + /* Cool... it's the right one */ + if (t->sessid != NULL) { + /* free previously allocated memory as we don't need it anymore */ + pool_free2(apools.sessid, t->sessid); + } + /* Store the sessid in the session for future use */ + if ((t->sessid = pool_alloc2(apools.sessid)) == NULL) { + Alert("Not enough Memory process_srv():asession->sessid:malloc().\n"); + send_log(t->be, LOG_ALERT, "Not enough Memory process_srv():asession->sessid:malloc().\n"); + return; + } + memcpy(t->sessid, value_begin, value_len); + t->sessid[value_len] = 0; } - /* Store the sessid in the session for future use */ - if ((t->sessid = pool_alloc2(apools.sessid)) == NULL) { - Alert("Not enough Memory process_srv():asession->sessid:malloc().\n"); - send_log(t->be, LOG_ALERT, "Not enough Memory process_srv():asession->sessid:malloc().\n"); - return; - } - memcpy(t->sessid, p3, t->be->appsession_len); - t->sessid[t->be->appsession_len] = 0; - }/* end if ((t->proxy->appsession_name != NULL) ... */ + } /* end if ((t->be->appsession_name != NULL) ... */ break; /* we don't want to loop again since there cannot be another cookie on the same line */ } /* we're now at the end of the cookie value */ /* keep the link from this header to next one */ @@ -4657,25 +4686,66 @@ void check_response_for_cacheability(struct session *t, struct buffer *rtr) */ void get_srv_from_appsession(struct session *t, const char *begin, int len) { - char *request_line; + char *end_params, *first_param, *cur_param, *next_param; + char separator; + int value_len; + + int mode = t->be->options2 & PR_O2_AS_M_ANY; if (t->be->appsession_name == NULL || - (t->txn.meth != HTTP_METH_GET && t->txn.meth != HTTP_METH_POST) || - (request_line = memchr(begin, ';', len)) == NULL || - ((1 + t->be->appsession_name_len + 1 + t->be->appsession_len) > (begin + len - request_line))) + (t->txn.meth != HTTP_METH_GET && t->txn.meth != HTTP_METH_POST)) { return; + } - /* skip ';' */ - request_line++; + first_param = NULL; + switch (mode) { + case PR_O2_AS_M_PP: + first_param = memchr(begin, ';', len); + break; + case PR_O2_AS_M_QS: + first_param = memchr(begin, '?', len); + break; + } - /* look if we have a jsessionid */ - if (strncasecmp(request_line, t->be->appsession_name, t->be->appsession_name_len) != 0) + if (first_param == NULL) { return; + } - /* skip jsessionid= */ - request_line += t->be->appsession_name_len + 1; + switch (mode) { + case PR_O2_AS_M_PP: + if ((end_params = memchr(first_param, '?', len - (begin - first_param))) == NULL) { + end_params = (char *) begin + len; + } + separator = ';'; + break; + case PR_O2_AS_M_QS: + end_params = (char *) begin + len; + separator = '&'; + break; + default: + /* unknown mode, shouldn't happen */ + return; + } - manage_client_side_appsession(t, request_line); + cur_param = next_param = end_params; + while (cur_param > first_param) { + cur_param--; + if ((cur_param[0] == separator) || (cur_param == first_param)) { + /* let's see if this is the appsession parameter */ + if ((cur_param + t->be->appsession_name_len + 1 < next_param) && + ((t->be->options2 & PR_O2_AS_PFX) || cur_param[t->be->appsession_name_len + 1] == '=') && + (strncasecmp(cur_param + 1, t->be->appsession_name, t->be->appsession_name_len) == 0)) { + /* Cool... it's the right one */ + cur_param += t->be->appsession_name_len + (t->be->options2 & PR_O2_AS_PFX ? 1 : 2); + value_len = MIN(t->be->appsession_len, next_param - cur_param); + if (value_len > 0) { + manage_client_side_appsession(t, cur_param, value_len); + } + break; + } + next_param = cur_param; + } + } #if defined(DEBUG_HASH) Alert("get_srv_from_appsession\n"); appsession_hash_dump(&(t->be->htbl_proxy));