From 240afa6d230dd37b1662a6c2ebabc52a3cefe38d Mon Sep 17 00:00:00 2001 From: willy tarreau Date: Sat, 17 Dec 2005 13:14:35 +0100 Subject: [PATCH] * released 1.1.11 * fixed multi-cookie handling in client request to allow clean deletion in insert+indirect mode. Now, only the server cookie is deleted and not all the header. Should now be compliant to RFC2109. * added a "nocache" option to "cookie" to specify that we explicitly want to add a "cache-control" header when we add a cookie. It is also possible to add an "Expires: " to keep compatibility with old/broken caches. * some doc and examples cleanups --- NOTES | 10 +-- TODO | 9 +- doc/haproxy.txt | 59 ++++++++++--- examples/{cfg => examples.cfg} | 0 examples/haproxy.cfg | 63 ++++++++++++++ examples/rc.highsock | 2 +- haproxy.c | 147 ++++++++++++++++++++++++--------- 7 files changed, 232 insertions(+), 58 deletions(-) rename examples/{cfg => examples.cfg} (100%) create mode 100644 examples/haproxy.cfg diff --git a/NOTES b/NOTES index 13b19bab0..78de2c16e 100644 --- a/NOTES +++ b/NOTES @@ -12,10 +12,8 @@ * full HTTP log with destination server ID, req and resp time. * source address of outgoing connections 1.1.8 -> 1.1.9 - - handle parametrable HTTP health-checks replies - - differentiate http headers and http uris - - support environment variables in config file - - support keep-alive 1.1.9 -> 1.1.10 - - automatically remove client cookie in insert+indirect mode - + * automatically remove client cookie in insert+indirect mode +1.1.10 -> 1.1.11 + * support multi-cookie as described in RFC2109 + * added "nocache" option to prevent caches from storing cookies. diff --git a/TODO b/TODO index d690e70b9..97236802d 100644 --- a/TODO +++ b/TODO @@ -18,5 +18,12 @@ - compter les matches - si match(n) & ([n].cpt > [n-1].cpt) & ([n].action == [n-1].action), swap(n,n-1) - régulièrement, diviser tous les compteurs (lors d'un dépassement par exemple) -- filtrage sur l'adresse IP source +- filtrage sur l'adresse IP source, et stocker le pointeur sur la dernière regex + matchée dans la "session" pour accélérer les regex. - gestion keep-alive + +- handle parametrable HTTP health-checks replies +- differentiate http headers and http uris +- support environment variables in config file +- support keep-alive + diff --git a/doc/haproxy.txt b/doc/haproxy.txt index 36c3c9945..23c2a0e5b 100644 --- a/doc/haproxy.txt +++ b/doc/haproxy.txt @@ -1,9 +1,9 @@ H A - P r o x y --------------- - version 1.1.10 + version 1.1.11 willy tarreau - 2002/05/10 + 2002/06/11 ================ | Introduction | @@ -417,7 +417,29 @@ Ce param 'balance'. -2.8) Définition du nom du cookie +2.8) Adresse de sortie +---------------------- +Il est possible de forcer l'adresse utilisée pour établir les connexions +vers les serveurs à l'aide du paramètre "source". Il est même possible de +forcer le port, bien que cette fonctionnalité se limite à des usages très +spécifiques. C'est particulièrement utile en cas d'adressage multiple, et +plus généralement pour permettre aux serveurs de trouver le chemin de +retour dans des contextes de routage difficiles. Si l'adresse est 0.0.0.0, +elle sera choisie librement par le systeme. Si le port est 0, il +sera choisi librement par le système. + +Exemples : +---------- + listen http_proxy 0.0.0.0:80 + # toutes les connexions prennent l'adresse 192.168.1.200 + source 192.168.1.200:0 + + listen rlogin_proxy 0.0.0.0:513 + # utiliser l'adresse 192.168.1.200 et le port réservé 900 + source 192.168.1.200:900 + + +2.9) Définition du nom du cookie -------------------------------- En mode HTTP, il est possible de rechercher la valeur d'un cookie pour savoir vers quel serveur aiguiller la requête utilisateur. Le nom du cookie est donné @@ -433,7 +455,8 @@ On peut modifier l'utilisation du cookie pour la rendre plus intelligente vis-à-vis des applications relayées. Il est possible, notamment de supprimer ou réécrire un cookie retourné par un serveur accédé en direct, et d'insérer un cookie dans une réponse HTTP adressée à un serveur sélectionné en répartition -de charge. +de charge, et même de signaler aux proxies amont de ne pas cacher le cookie +inséré. Exemples : ---------- @@ -455,6 +478,12 @@ serveurs aient un cookie renseign cookie SERVERID insert +Pour insérer un cookie, en s'assurant qu'un cache en amont ne le stockera pas, +ajouter le mot clé 'nocache' après 'insert' : + + cookie SERVERID insert nocache + + Remarques : ----------- - Il est possible de combiner 'insert' avec 'indirect' ou 'rewrite' pour s'adapter @@ -463,8 +492,13 @@ Remarques : - dans le cas où 'insert' et 'indirect' sont spécifiés, le cookie n'est jamais transmis au serveur vu qu'il n'en a pas connaissance et ne pourrait pas le comprendre. +- il est particulièrement recommandé d'utiliser 'nocache' en mode insertion si + des caches peuvent se trouver entre les clients et l'instance du proxy. Dans + le cas contraire, un cache HTTP 1.0 pourrait cacher la réponse, incluant le + cookie de persistence inséré, donc provoquer des changements de serveurs pour + des clients partageant le même cache. -2.9) Assignation d'un serveur à une valeur de cookie +2.10) Assignation d'un serveur à une valeur de cookie ---------------------------------------------------- En mode HTTP, il est possible d'associer des serveurs à des valeurs de cookie par le paramètre 'server'. La syntaxe est : @@ -536,10 +570,11 @@ Exemples : server web2 192.168.1.2:80 cookie server02 check inter 500 rise 1 fall 2 # Insertion automatique de cookie dans la réponse du serveur, et suppression -# automatique dans la requête. +# automatique dans la requête, tout en indiquant aux caches de ne pas garder +# ce cookie. listen web_appl 0.0.0.0:80 mode http - cookie SERVERID insert indirect + cookie SERVERID insert nocache indirect balance roundrobin server web1 192.168.1.1:80 cookie server01 check server web2 192.168.1.2:80 cookie server02 check @@ -667,7 +702,7 @@ La syntaxe est : reqallow autoriser une requête qui valide reqiallow idem sans distinction majuscules/minuscules reqdeny interdire une requête qui valide - reqdeny idem sans distinction majuscules/minuscules + reqideny idem sans distinction majuscules/minuscules rspadd pour ajouter un en-tête dans la réponse rsprep pour modifier la réponse @@ -733,14 +768,15 @@ permet d'assurer une persistence dans les sessions HTTP d'une mani pratiquement transparente pour les applications. Le principe est simple : - attribuer une valeur d'un cookie à chaque serveur - effectuer une répartition interne - - insérer un cookie dans les réponses issues d'une répartition uniquement - - cacher ce cookie à l'application + - insérer un cookie dans les réponses issues d'une répartition uniquement, + et faire en sorte que des caches ne mémorisent pas ce cookie. + - cacher ce cookie à l'application lors des requêtes ultérieures. Exemple : ------- listen application 0.0.0.0:80 mode http - cookie SERVERID insert indirect + cookie SERVERID insert nocache indirect balance roundrobin server 192.168.1.1:80 cookie server01 check server 192.168.1.2:80 cookie server02 check @@ -772,7 +808,6 @@ if [ -e /proc/sys/net/ipv4/netfilter/ip_ct_tcp_timeout_fin_wait ]; then fi echo 1024 60999 > /proc/sys/net/ipv4/ip_local_port_range -echo 32768 > /proc/sys/net/ipv4/ip_queue_maxlen echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout echo 4096 > /proc/sys/net/ipv4/tcp_max_syn_backlog echo 262144 > /proc/sys/net/ipv4/tcp_max_tw_buckets diff --git a/examples/cfg b/examples/examples.cfg similarity index 100% rename from examples/cfg rename to examples/examples.cfg diff --git a/examples/haproxy.cfg b/examples/haproxy.cfg new file mode 100644 index 000000000..44fd8d7bb --- /dev/null +++ b/examples/haproxy.cfg @@ -0,0 +1,63 @@ +global + log 127.0.0.1 local0 + maxconn 4096 + chroot /tmp + uid 11 + gid 2 + daemon + #debug + #quiet + +listen appli1-rewrite 0.0.0.0:10001 + log global + mode http + option httplog + option dontlognull + cookie SERVERID rewrite + balance roundrobin + server app1_1 192.168.34.23:8080 cookie app1inst1 check inter 2000 rise 2 fall 5 + server app1_2 192.168.34.32:8080 cookie app1inst2 check inter 2000 rise 2 fall 5 + server app1_3 192.168.34.27:8080 cookie app1inst3 check inter 2000 rise 2 fall 5 + server app1_4 192.168.34.42:8080 cookie app1inst4 check inter 2000 rise 2 fall 5 + retries 3 + redispatch + maxconn 2000 + contimeout 5000 + clitimeout 50000 + srvtimeout 50000 + +listen appli2-insert 0.0.0.0:10002 + log global + mode http + option httplog + option dontlognull + balance roundrobin + cookie SERVERID insert indirect nocache + server inst1 192.168.114.56:80 cookie server01 check inter 2000 fall 3 + server inst2 192.168.114.56:81 cookie server02 check inter 2000 fall 3 + retries 3 + redispatch + maxconn 2000 + contimeout 5000 + clitimeout 50000 + srvtimeout 50000 + + reqidel ^Connection: # desactivation du keep-alive + reqadd Connection:\ close + rspidel ^Connection: + rspadd Connection:\ close + rspidel ^Set-cookie:\ IP= # ne pas laisser sortir une adresse privee + +listen appli3-relais 0.0.0.0:10003 + log global + mode http + option httplog + option dontlognull + dispatch 192.168.135.17:80 + retries 3 + redispatch + maxconn 2000 + contimeout 5000 + clitimeout 50000 + srvtimeout 50000 + diff --git a/examples/rc.highsock b/examples/rc.highsock index 9325ee062..d85935af1 100644 --- a/examples/rc.highsock +++ b/examples/rc.highsock @@ -17,7 +17,7 @@ if [ -e /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_fin_wait ]; then fi echo 1024 60999 > /proc/sys/net/ipv4/ip_local_port_range -echo 32768 > /proc/sys/net/ipv4/ip_queue_maxlen +#echo 32768 > /proc/sys/net/ipv4/ip_queue_maxlen echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout echo 4096 > /proc/sys/net/ipv4/tcp_max_syn_backlog echo 262144 > /proc/sys/net/ipv4/tcp_max_tw_buckets diff --git a/haproxy.c b/haproxy.c index 9ab37b1c5..e6721d7cc 100644 --- a/haproxy.c +++ b/haproxy.c @@ -17,6 +17,14 @@ * * ChangeLog : * + * 2002/06/04 : 1.1.11 + * - fixed multi-cookie handling in client request to allow clean deletion + * in insert+indirect mode. Now, only the server cookie is deleted and not + * all the header. Should now be compliant to RFC2109. + * - added a "nocache" option to "cookie" to specify that we explicitly want + * to add a "cache-control" header when we add a cookie. + * It is also possible to add an "Expires: " to keep compatibility + * with old/broken caches. * 2002/05/10 : 1.1.10 * - if a cookie is used in insert+indirect mode, it's desirable that the * the servers don't see it. It was not possible to remove it correctly @@ -145,8 +153,8 @@ #include #endif -#define HAPROXY_VERSION "1.1.9" -#define HAPROXY_DATE "2002/04/19" +#define HAPROXY_VERSION "1.1.11" +#define HAPROXY_DATE "2002/06/04" /* this is for libc5 for example */ #ifndef TCP_NODELAY @@ -313,6 +321,7 @@ int strlcpy(char *dst, const char *src, int size) { #define PR_O_FWDFOR 128 /* insert x-forwarded-for with client address */ #define PR_O_BIND_SRC 256 /* bind to a specific source address when connect()ing */ #define PR_O_NULLNOLOG 512 /* a connect without request will not be logged */ +#define PR_O_COOK_NOC 1024 /* add a 'Cache-control' header with the cookie */ /* various session flags */ @@ -2021,7 +2030,9 @@ int buffer_replace(struct buffer *b, char *pos, char *end, char *str) { return delta; } -/* same except that the string len is given */ +/* same except that the string len is given, which allows str to be NULL if + * len is 0. + */ int buffer_replace2(struct buffer *b, char *pos, char *end, char *str, int len) { int delta; @@ -2034,7 +2045,8 @@ int buffer_replace2(struct buffer *b, char *pos, char *end, char *str, int len) memmove(end + delta, end, b->data + b->l - end); /* now, copy str over pos */ - memcpy(pos, str,len); + if (len) + memcpy(pos, str, len); /* we only move data after the displaced zone */ if (b->r > pos) b->r += delta; @@ -2247,43 +2259,59 @@ int process_cli(struct session *t) { *ptr = term; /* restore the string terminator */ } - /* now look for cookies */ - if (!delete_header && (req->r >= req->h + 8) && (t->proxy->cookie_name != NULL) + /* Now look for cookies. Conforming to RFC2109, we have to support + * attributes whose name begin with a '$', and associate them with + * the right cookie, if we want to delete this cookie. + * So there are 3 cases for each cookie read : + * 1) it's a special attribute, beginning with a '$' : ignore it. + * 2) it's a server id cookie that we *MAY* want to delete : save + * some pointers on it (last semi-colon, beginning of cookie...) + * 3) it's an application cookie : we *MAY* have to delete a previous + * "special" cookie. + * At the end of loop, if a "special" cookie remains, we may have to + * remove it. If no application cookie persists in the header, we + * *MUST* delete it + */ + if (!delete_header && (t->proxy->cookie_name != NULL) + && !(t->flags & SN_CLDENY) && (ptr >= req->h + 8) && (strncmp(req->h, "Cookie: ", 8) == 0)) { char *p1, *p2, *p3, *p4; - + char *del_colon, *del_cookie, *colon; + int app_cookies; + p1 = req->h + 8; /* first char after 'Cookie: ' */ + colon = p1; + /* del_cookie == NULL => nothing to be deleted */ + del_colon = del_cookie = NULL; + app_cookies = 0; while (p1 < ptr) { - while (p1 < ptr && (isspace((int)*p1) || *p1 == ';')) + /* skip spaces and colons, but keep an eye on these ones */ + while (p1 < ptr) { + if (*p1 == ';' || *p1 == ',') + colon = p1; + else if (!isspace((int)*p1)) + break; p1++; + } if (p1 == ptr) break; - else if (*p1 == ';') { /* next cookie */ - ++p1; - continue; - } /* p1 is at the beginning of the cookie name */ p2 = p1; - - while (p2 < ptr && *p2 != '=' && *p2 != ';') + while (p2 < ptr && *p2 != '=') p2++; if (p2 == ptr) break; - else if (*p2 == ';') { /* next cookie */ - p1=++p2; - continue; - } p3 = p2 + 1; /* skips the '=' sign */ if (p3 == ptr) break; - p4=p3; - while (p4 < ptr && !isspace((int)*p4) && *p4 != ';') + p4 = p3; + while (p4 < ptr && !isspace((int)*p4) && *p4 != ';' && *p4 != ',') p4++; /* here, we have the cookie name between p1 and p2, @@ -2291,8 +2319,11 @@ int process_cli(struct session *t) { * we can process it. */ - if ((p2 - p1 == strlen(t->proxy->cookie_name)) && - (strncmp(p1, t->proxy->cookie_name, p2 - p1) == 0)) { + if (*p1 == '$') { + /* skip this one */ + } + else if ((p2 - p1 == strlen(t->proxy->cookie_name)) && + (memcmp(p1, t->proxy->cookie_name, p2 - p1) == 0)) { /* Cool... it's the right one */ struct server *srv = t->proxy->srv; @@ -2304,31 +2335,64 @@ int process_cli(struct session *t) { if (srv) { /* we found the server */ t->flags |= SN_DIRECT; t->srv = srv; - /* if this cookie was set in insert+indirect mode, then it's better that the - * server never sees it. - */ - if ((t->proxy->options & (PR_O_COOK_INS | PR_O_COOK_IND)) == (PR_O_COOK_INS | PR_O_COOK_IND)) - delete_header = 1; } - - break; + /* if this cookie was set in insert+indirect mode, then it's better that the + * server never sees it. + */ + if (del_cookie == NULL && + (t->proxy->options & (PR_O_COOK_INS | PR_O_COOK_IND)) == (PR_O_COOK_INS | PR_O_COOK_IND)) { + del_cookie = p1; + del_colon = colon; + } } else { - // fprintf(stderr,"Ignoring unknown cookie : "); - // write(2, p1, p2-p1); - // fprintf(stderr," = "); - // write(2, p3, p4-p3); - // fprintf(stderr,"\n"); + /* now we know that we must keep this cookie since it's + * not ours. But if we wanted to delete our cookie + * earlier, we cannot remove the complete header, but we + * can remove the previous block itself. + */ + app_cookies++; + + if (del_cookie != NULL) { + buffer_replace2(req, del_cookie, p1, NULL, 0); + p4 -= (p1 - del_cookie); + ptr -= (p1 - del_cookie); + del_cookie = del_colon = NULL; + } } + /* we'll have to look for another cookie ... */ p1 = p4; } /* while (p1 < ptr) */ - } /* end of cookie processing */ + + /* There's no more cookie on this line. + * We may have marked the last one(s) for deletion. + * We must do this now in two ways : + * - if there is no app cookie, we simply delete the header ; + * - if there are app cookies, we must delete the end of the + * string properly, including the colon/semi-colon before + * the cookie name. + */ + if (del_cookie != NULL) { + if (app_cookies) { + buffer_replace2(req, del_colon, ptr, NULL, 0); + /* WARNING! becomes invalid for now. If some code + * below needs to rely on it before the end of the global + * header loop, we need to correct it with this code : + * ptr = del_colon; + */ + } + else + delete_header = 1; + } + } /* end of cookie processing on this header */ /* let's look if we have to delete this header */ if (delete_header && !(t->flags & SN_CLDENY)) { - buffer_replace2(req, req->h, req->lr, "", 0); + buffer_replace2(req, req->h, req->lr, NULL, 0); } + /* WARNING: ptr is not valid anymore, since the header may have been deleted or truncated ! */ + req->h = req->lr; } /* while (req->lr < req->r) */ @@ -2634,6 +2698,9 @@ int process_srv(struct session *t) { */ len = sprintf(newhdr, "Set-Cookie: %s=%s; path=/\r\n", t->proxy->cookie_name, t->srv->cookie); + if (t->proxy->options & PR_O_COOK_NOC) + len += sprintf(newhdr + len, "Cache-control: no-cache=\"set-cookie\"\r\n"); + buffer_replace2(rep, rep->h, rep->h, newhdr, len); } @@ -2731,8 +2798,9 @@ int process_srv(struct session *t) { } /* check for server cookies */ - if (!delete_header && (t->proxy->options & PR_O_COOK_ANY) && (rep->r >= rep->h + 12) && - (t->proxy->cookie_name != NULL) && (strncmp(rep->h, "Set-Cookie: ", 12) == 0)) { + if (!delete_header && (t->proxy->options & PR_O_COOK_ANY) + && (t->proxy->cookie_name != NULL) && (ptr >= rep->h + 12) + && (strncmp(rep->h, "Set-Cookie: ", 12) == 0)) { char *p1, *p2, *p3, *p4; p1 = rep->h + 12; /* first char after 'Set-Cookie: ' */ @@ -3709,6 +3777,9 @@ int cfg_parse_listen(char *file, int linenum, char **args) { else if (!strcmp(args[cur_arg], "insert")) { curproxy->options |= PR_O_COOK_INS; } + else if (!strcmp(args[cur_arg], "nocache")) { + curproxy->options |= PR_O_COOK_NOC; + } else { Alert("parsing [%s:%d] : supports 'rewrite', 'insert' and 'indirect' options.\n", file, linenum);