* 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
diff --git a/NOTES b/NOTES
index 13b19ba..78de2c1 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 d690e70..9723680 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 36c3c99..23c2a0e 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 @@
'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 @@
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 @@
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 @@
- 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 @@
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 @@
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 @@
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 @@
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 0000000..44fd8d7
--- /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 9325ee0..d85935a 100644
--- a/examples/rc.highsock
+++ b/examples/rc.highsock
@@ -17,7 +17,7 @@
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 9ab37b1..e6721d7 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: <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 @@
#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 @@
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 @@
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 @@
*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 @@
* 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 @@
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 @@
*/
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 @@
}
/* 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 @@
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);