diff --git a/doc/configuration.txt b/doc/configuration.txt index 73e760f99..44e87dab4 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -861,7 +861,7 @@ acl [flags] [operator] ... See section 7 about ACL usage. -appsession len timeout +appsession len timeout [request-learn] Define session stickiness on an existing application cookie. May be used in sections : defaults | frontend | listen | backend no | no | yes | yes @@ -876,6 +876,14 @@ appsession len timeout memory if unused. If no unit is specified, this time is in milliseconds. + request-learn + If this option is specified, then haproxy will be able to learn + the cookie found in the request in case the server does not + specify any in response. This is typically what happens with + PHPSESSID cookies, or when haproxy's session expires before + the application's session and the correct server is selected. + It is recommended to specify this option to improve reliability. + 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 diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 5a129986e..5fcbced81 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -74,6 +74,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_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 ac4b4198d..ec00d741e 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -123,6 +123,7 @@ #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 */ struct error_snapshot { struct timeval when; /* date of this event, (tv_sec == 0) means "never" */ diff --git a/include/types/session.h b/include/types/session.h index a8734ff10..6e2344f87 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -162,6 +162,7 @@ struct session { int conn_retries; /* number of connect retries left */ int flags; /* some flags describing the session */ unsigned term_trace; /* term trace: 4*8 bits indicating which part of the code closed */ + char *sessid; /* the session id, if found in the request or in the response */ struct buffer *req; /* request buffer */ struct buffer *rep; /* response buffer */ struct stream_interface si[2]; /* client and server stream interfaces */ diff --git a/src/cfgparse.c b/src/cfgparse.c index fd5c626e3..bcadaee59 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -1535,6 +1535,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) } } else if (!strcmp(args[0], "appsession")) { /* cookie name */ + int cur_arg; if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) err_code |= ERR_WARN; @@ -1564,6 +1565,14 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) err_code |= ERR_ALERT | ERR_ABORT; goto out; } + + cur_arg = 6; + curproxy->options2 &= ~PR_O2_AS_REQL; + while (*(args[cur_arg])) { + if (!strcmp(args[cur_arg], "request-learn")) + curproxy->options2 |= PR_O2_AS_REQL; + cur_arg++; + } } /* Url App Session */ else if (!strcmp(args[0], "capture")) { if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL)) diff --git a/src/client.c b/src/client.c index 68a192d26..79bb9d941 100644 --- a/src/client.c +++ b/src/client.c @@ -185,6 +185,7 @@ int event_accept(int fd) { s->be = s->fe = p; s->req = s->rep = NULL; /* will be allocated later */ + s->sessid = NULL; s->si[0].state = s->si[0].prev_state = SI_ST_EST; s->si[0].err_type = SI_ET_NONE; diff --git a/src/proto_http.c b/src/proto_http.c index 6aade9fb4..9f50d4619 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -3577,6 +3577,72 @@ 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) { + struct http_txn *txn = &t->txn; + appsess *asession = NULL; + char *sessid_temp = NULL; + + 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) { + /* free previously allocated memory as we don't need the session id found in the URL anymore */ + pool_free2(apools.sessid, t->sessid); + } + + if ((t->sessid = pool_alloc2(apools.sessid)) == NULL) { + Alert("Not enough memory process_cli():asession->sessid:malloc().\n"); + send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession->sessid:malloc().\n"); + return; + } + + memcpy(t->sessid, buf, t->be->appsession_len); + t->sessid[t->be->appsession_len] = 0; + } + + if ((sessid_temp = pool_alloc2(apools.sessid)) == NULL) { + Alert("Not enough memory process_cli():asession->sessid:malloc().\n"); + send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession->sessid:malloc().\n"); + return; + } + + memcpy(sessid_temp, buf, t->be->appsession_len); + sessid_temp[t->be->appsession_len] = 0; + + asession = appsession_hash_lookup(&(t->be->htbl_proxy), sessid_temp); + /* free previously allocated memory */ + pool_free2(apools.sessid, sessid_temp); + + if (asession != NULL) { + asession->expire = tick_add_ifset(now_ms, t->be->timeout.appsession); + if (!(t->be->options2 & PR_O2_AS_REQL)) + asession->request_count++; + + if (asession->serverid != NULL) { + struct server *srv = t->be->srv; + while (srv) { + if (strcmp(srv->id, asession->serverid) == 0) { + if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) { + /* we found the server and it's usable */ + txn->flags &= ~TX_CK_MASK; + txn->flags |= TX_CK_VALID; + t->flags |= SN_DIRECT | SN_ASSIGNED; + t->srv = srv; + break; + } else { + txn->flags &= ~TX_CK_MASK; + txn->flags |= TX_CK_DOWN; + } + } + srv = srv->next; + } + } + } +} + /* * Manage client-side cookie. It can impact performance by about 2% so it is * desirable to call it only when needed. @@ -3588,9 +3654,6 @@ void manage_client_side_cookies(struct session *t, struct buffer *req) char *del_colon, *del_cookie, *colon; int app_cookies; - appsess *asession_temp = NULL; - appsess local_asession; - char *cur_ptr, *cur_end, *cur_next; int cur_idx, old_idx; @@ -3820,63 +3883,7 @@ void manage_client_side_cookies(struct session *t, struct buffer *req) /* first, let's see if the cookie is our appcookie*/ /* Cool... it's the right one */ - - asession_temp = &local_asession; - - if ((asession_temp->sessid = pool_alloc2(apools.sessid)) == NULL) { - Alert("Not enough memory process_cli():asession->sessid:malloc().\n"); - send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession->sessid:malloc().\n"); - return; - } - - memcpy(asession_temp->sessid, p3, t->be->appsession_len); - asession_temp->sessid[t->be->appsession_len] = 0; - asession_temp->serverid = NULL; - - /* only do insert, if lookup fails */ - asession_temp = appsession_hash_lookup(&(t->be->htbl_proxy), asession_temp->sessid); - if (asession_temp == NULL) { - if ((asession_temp = pool_alloc2(pool2_appsess)) == NULL) { - /* free previously allocated memory */ - pool_free2(apools.sessid, local_asession.sessid); - Alert("Not enough memory process_cli():asession:calloc().\n"); - send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n"); - return; - } - - asession_temp->sessid = local_asession.sessid; - asession_temp->serverid = local_asession.serverid; - asession_temp->request_count = 0; - appsession_hash_insert(&(t->be->htbl_proxy), asession_temp); - } else { - /* free previously allocated memory */ - pool_free2(apools.sessid, local_asession.sessid); - } - if (asession_temp->serverid == NULL) { - /* TODO redispatch request */ - Alert("Found Application Session without matching server.\n"); - } else { - struct server *srv = t->be->srv; - while (srv) { - if (strcmp(srv->id, asession_temp->serverid) == 0) { - if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) { - /* we found the server and it's usable */ - txn->flags &= ~TX_CK_MASK; - txn->flags |= TX_CK_VALID; - t->flags |= SN_DIRECT | SN_ASSIGNED; - t->srv = srv; - break; - } else { - txn->flags &= ~TX_CK_MASK; - txn->flags |= TX_CK_DOWN; - } - } - srv = srv->next; - }/* end while(srv) */ - }/* end else if server == NULL */ - - asession_temp->expire = tick_add_ifset(now_ms, t->be->timeout.appsession); - asession_temp->request_count++; + manage_client_side_appsession(t, p3); #if defined(DEBUG_HASH) Alert("manage_client_side_cookies\n"); appsession_hash_dump(&(t->be->htbl_proxy)); @@ -4154,9 +4161,6 @@ void manage_server_side_cookies(struct session *t, struct buffer *rtr) struct http_txn *txn = &t->txn; char *p1, *p2, *p3, *p4; - appsess *asession_temp = NULL; - appsess local_asession; - char *cur_ptr, *cur_end, *cur_next; int cur_idx, old_idx, delta; @@ -4297,60 +4301,64 @@ void manage_server_side_cookies(struct session *t, struct buffer *rtr) /* Cool... it's the right one */ - size_t server_id_len = strlen(t->srv->id) + 1; - asession_temp = &local_asession; - - if ((asession_temp->sessid = pool_alloc2(apools.sessid)) == NULL) { + 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(asession_temp->sessid, p3, t->be->appsession_len); - asession_temp->sessid[t->be->appsession_len] = 0; - asession_temp->serverid = NULL; - - /* only do insert, if lookup fails */ - asession_temp = appsession_hash_lookup(&(t->be->htbl_proxy), asession_temp->sessid); - if (asession_temp == NULL) { - if ((asession_temp = pool_alloc2(pool2_appsess)) == NULL) { - Alert("Not enough Memory process_srv():asession:calloc().\n"); - send_log(t->be, LOG_ALERT, "Not enough Memory process_srv():asession:calloc().\n"); - return; - } - asession_temp->sessid = local_asession.sessid; - asession_temp->serverid = local_asession.serverid; - asession_temp->request_count = 0; - appsession_hash_insert(&(t->be->htbl_proxy), asession_temp); - } else { - /* free wasted memory */ - pool_free2(apools.sessid, local_asession.sessid); - } - - if (asession_temp->serverid == NULL) { - if ((asession_temp->serverid = pool_alloc2(apools.serverid)) == 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; - } - asession_temp->serverid[0] = '\0'; - } - - if (asession_temp->serverid[0] == '\0') - memcpy(asession_temp->serverid, t->srv->id, server_id_len); - - asession_temp->expire = tick_add_ifset(now_ms, t->be->timeout.appsession); - asession_temp->request_count++; -#if defined(DEBUG_HASH) - Alert("manage_server_side_cookies\n"); - appsession_hash_dump(&(t->be->htbl_proxy)); -#endif + memcpy(t->sessid, p3, t->be->appsession_len); + t->sessid[t->be->appsession_len] = 0; }/* end if ((t->proxy->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 */ old_idx = cur_idx; } /* end of cookie processing on this header */ + + if (t->sessid != NULL) { + appsess *asession = NULL; + /* only do insert, if lookup fails */ + asession = appsession_hash_lookup(&(t->be->htbl_proxy), t->sessid); + if (asession == NULL) { + if ((asession = pool_alloc2(pool2_appsess)) == NULL) { + Alert("Not enough Memory process_srv():asession:calloc().\n"); + send_log(t->be, LOG_ALERT, "Not enough Memory process_srv():asession:calloc().\n"); + return; + } + if ((asession->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(asession->sessid, t->sessid, t->be->appsession_len); + asession->sessid[t->be->appsession_len] = 0; + + size_t server_id_len = strlen(t->srv->id) + 1; + if ((asession->serverid = pool_alloc2(apools.serverid)) == 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; + } + asession->serverid[0] = '\0'; + memcpy(asession->serverid, t->srv->id, server_id_len); + + asession->request_count = 0; + appsession_hash_insert(&(t->be->htbl_proxy), asession); + } + + asession->expire = tick_add_ifset(now_ms, t->be->timeout.appsession); + asession->request_count++; + } + +#if defined(DEBUG_HASH) + Alert("manage_server_side_cookies\n"); + appsession_hash_dump(&(t->be->htbl_proxy)); +#endif } @@ -4448,9 +4456,6 @@ void check_response_for_cacheability(struct session *t, struct buffer *rtr) */ void get_srv_from_appsession(struct session *t, const char *begin, int len) { - struct http_txn *txn = &t->txn; - appsess *asession_temp = NULL; - appsess local_asession; char *request_line; if (t->be->appsession_name == NULL || @@ -4469,69 +4474,11 @@ void get_srv_from_appsession(struct session *t, const char *begin, int len) /* skip jsessionid= */ request_line += t->be->appsession_name_len + 1; - /* First try if we already have an appsession */ - asession_temp = &local_asession; - - if ((asession_temp->sessid = pool_alloc2(apools.sessid)) == NULL) { - Alert("Not enough memory process_cli():asession_temp->sessid:calloc().\n"); - send_log(t->be, LOG_ALERT, "Not enough Memory process_cli():asession_temp->sessid:calloc().\n"); - return; - } - - /* Copy the sessionid */ - memcpy(asession_temp->sessid, request_line, t->be->appsession_len); - asession_temp->sessid[t->be->appsession_len] = 0; - asession_temp->serverid = NULL; - - /* only do insert, if lookup fails */ - asession_temp = appsession_hash_lookup(&(t->be->htbl_proxy), asession_temp->sessid); - if (asession_temp == NULL) { - if ((asession_temp = pool_alloc2(pool2_appsess)) == NULL) { - /* free previously allocated memory */ - pool_free2(apools.sessid, local_asession.sessid); - Alert("Not enough memory process_cli():asession:calloc().\n"); - send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n"); - return; - } - asession_temp->sessid = local_asession.sessid; - asession_temp->serverid = local_asession.serverid; - asession_temp->request_count=0; - appsession_hash_insert(&(t->be->htbl_proxy), asession_temp); - } - else { - /* free previously allocated memory */ - pool_free2(apools.sessid, local_asession.sessid); - } - - asession_temp->expire = tick_add_ifset(now_ms, t->be->timeout.appsession); - asession_temp->request_count++; - + manage_client_side_appsession(t, request_line); #if defined(DEBUG_HASH) Alert("get_srv_from_appsession\n"); appsession_hash_dump(&(t->be->htbl_proxy)); #endif - if (asession_temp->serverid == NULL) { - /* TODO redispatch request */ - Alert("Found Application Session without matching server.\n"); - } else { - struct server *srv = t->be->srv; - while (srv) { - if (strcmp(srv->id, asession_temp->serverid) == 0) { - if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) { - /* we found the server and it's usable */ - txn->flags &= ~TX_CK_MASK; - txn->flags |= TX_CK_VALID; - t->flags |= SN_DIRECT | SN_ASSIGNED; - t->srv = srv; - break; - } else { - txn->flags &= ~TX_CK_MASK; - txn->flags |= TX_CK_DOWN; - } - } - srv = srv->next; - } - } } diff --git a/src/session.c b/src/session.c index 447660e2f..fefdc8648 100644 --- a/src/session.c +++ b/src/session.c @@ -77,6 +77,9 @@ void session_free(struct session *s) pool_free2(pool2_buffer, s->req); pool_free2(pool2_buffer, s->rep); + if (s->sessid) + pool_free2(apools.sessid, s->sessid); + if (fe) { pool_free2(fe->hdr_idx_pool, txn->hdr_idx.v);