* 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: <old-date>" to keep compatibility
  with old/broken caches.
* some doc and examples cleanups
This commit is contained in:
willy tarreau 2005-12-17 13:14:35 +01:00
parent 96d4037194
commit 240afa6d23
7 changed files with 232 additions and 58 deletions

10
NOTES
View File

@ -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.

9
TODO
View File

@ -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

View File

@ -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 <search> autoriser une requête qui valide <search>
reqiallow <search> idem sans distinction majuscules/minuscules
reqdeny <search> interdire une requête qui valide <search>
reqdeny <search> idem sans distinction majuscules/minuscules
reqideny <search> idem sans distinction majuscules/minuscules
rspadd <string> pour ajouter un en-tête dans la réponse
rsprep <search> <replace> 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

63
examples/haproxy.cfg Normal file
View File

@ -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

View File

@ -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

147
haproxy.c
View File

@ -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: <old-date>" 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 <linux/netfilter_ipv4.h>
#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! <ptr> 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] : <cookie> supports 'rewrite', 'insert' and 'indirect' options.\n",
file, linenum);