[MINOR] add X-Original-To: header

I have attached a patch which will add on every http request a new
header 'X-Original-To'. If you have HAProxy running in transparent mode
with a big number of SQUID servers behind it, it is very nice to have
the original destination ip as a common header to make decisions based
on it.

The whole thing is configurable with a new option 'originalto'. I have
updated the sourcecode as well as the documentation. The 'haproxy-en.txt'
and 'haproxy-fr.txt' files are untouched, due to lack of my french
language knowledge. ;)

Also the patch adds this header for IPv4 only. I haven't any IPv6 test
environment running here and don't know if getsockopt() with SO_ORIGINAL_DST
will work on IPv6. If someone knows it and wants to test it I can modify
the diff. Feel free to ask me questions or things which should be changed. :)

--Maik
This commit is contained in:
Maik Broemme 2009-04-17 18:53:21 +02:00 committed by Willy Tarreau
parent 3909a2ab00
commit 2850cb42b6
6 changed files with 171 additions and 2 deletions

View File

@ -128,6 +128,15 @@ Limits :
option httpclose
option forwardfor
- if the application needs to log the original destination IP, use the
"originalto" option which will add an "X-Original-To" header with the
original destination IP address. You must also use "httpclose" to ensure
that you will rewrite every requests and not only the first one of each
session :
option httpclose
option originalto
The web server will have to be configured to use this header instead.
For example, on apache, you can use LogFormat for this :

View File

@ -603,6 +603,7 @@ monitor-uri X X X -
[no] option dontlognull X X X -
[no] option forceclose X - X X
option forwardfor X X X X
option originalto X X X X
[no] option http_proxy X X X X
option httpchk X - X X
[no] option httpclose X X X X
@ -2108,6 +2109,65 @@ option forwardfor [ except <network> ] [ header <name> ]
See also : "option httpclose"
option originalto [ except <network> ] [ header <name> ]
Enable insertion of the X-Original-To header to requests sent to servers
May be used in sections : defaults | frontend | listen | backend
yes | yes | yes | yes
Arguments :
<network> is an optional argument used to disable this option for sources
matching <network>
<name> an optional argument to specify a different "X-Original-To"
header name.
Since HAProxy can work in transparent mode, every request from a client can
be redirected to the proxy and HAProxy itself can proxy every request to a
complex SQUID environment and the destination host from SO_ORIGINAL_DST will
be lost. This is annoying when you want access rules based on destination ip
addresses. To solve this problem, a new HTTP header "X-Original-To" may be
added by HAProxy to all requests sent to the server. This header contains a
value representing the original destination IP address. Since this must be
configured to always use the last occurrence of this header only. Note that
only the last occurrence of the header must be used, since it is really
possible that the client has already brought one.
The keyword "header" may be used to supply a different header name to replace
the default "X-Original-To". This can be useful where you might already
have a "X-Original-To" header from a different application, and you need
preserve it. Also if your backend server doesn't use the "X-Original-To"
header and requires different one.
Sometimes, a same HAProxy instance may be shared between a direct client
access and a reverse-proxy access (for instance when an SSL reverse-proxy is
used to decrypt HTTPS traffic). It is possible to disable the addition of the
header for a known source address or network by adding the "except" keyword
followed by the network address. In this case, any source IP matching the
network will not cause an addition of this header. Most common uses are with
private networks or 127.0.0.1.
This option may be specified either in the frontend or in the backend. If at
least one of them uses it, the header will be added. Note that the backend's
setting of the header subargument takes precedence over the frontend's if
both are defined.
It is important to note that as long as HAProxy does not support keep-alive
connections, only the first request of a connection will receive the header.
For this reason, it is important to ensure that "option httpclose" is set
when using this option.
Examples :
# Original Destination address
frontend www
mode http
option originalto except 127.0.0.1
# Those servers want the IP Address in X-Client-Dst
backend www
mode http
option originalto header X-Client-Dst
See also : "option httpclose"
option http_proxy
no option http_proxy
Enable or disable plain HTTP proxy mode

View File

@ -130,6 +130,9 @@
// X-Forwarded-For header default
#define DEF_XFORWARDFOR_HDR "X-Forwarded-For"
// X-Original-To header default
#define DEF_XORIGINALTO_HDR "X-Original-To"
/* Default connections limit.
*
* A system limit can be enforced at build time in order to avoid using haproxy

View File

@ -105,7 +105,8 @@
#define PR_O_CONTSTATS 0x10000000 /* continous counters */
#define PR_O_HTTP_PROXY 0x20000000 /* Enable session to use HTTP proxy operations */
#define PR_O_DISABLE404 0x40000000 /* Disable a server on a 404 response to a health-check */
/* unused: 0x80000000 */
#define PR_O_ORGTO 0x80000000 /* insert x-original-to with destination address */
/* unused: 0x80000000 - now used by PR_O_ORGTO */
/* bits for proxy->options2 */
#define PR_O2_SPLIC_REQ 0x00000001 /* transfer requests using linux kernel's splice() */
@ -231,8 +232,12 @@ struct proxy {
unsigned int fe_maxsps; /* max # of new sessions per second on the frontend */
unsigned int fullconn; /* #conns on backend above which servers are used at full load */
struct in_addr except_net, except_mask; /* don't x-forward-for for this address. FIXME: should support IPv6 */
struct in_addr except_to; /* don't x-original-to for this address. */
struct in_addr except_mask_to; /* the netmask for except_to. */
char *fwdfor_hdr_name; /* header to use - default: "x-forwarded-for" */
int fwdfor_hdr_len; /* length of "x-forwarded-for" header */
char *orgto_hdr_name; /* header to use - default: "x-original-to" */
int orgto_hdr_len; /* length of "x-original-to" header */
unsigned down_trans; /* up-down transitions */
unsigned down_time; /* total time the proxy was down */

View File

@ -763,6 +763,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
curproxy->lbprm.algo = defproxy.lbprm.algo;
curproxy->except_net = defproxy.except_net;
curproxy->except_mask = defproxy.except_mask;
curproxy->except_mask_to = defproxy.except_mask_to;
if (defproxy.fwdfor_hdr_len) {
curproxy->fwdfor_hdr_len = defproxy.fwdfor_hdr_len;
@ -1722,6 +1723,51 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
}
} /* end while loop */
}
else if (!strcmp(args[1], "originalto")) {
int cur_arg;
/* insert x-original-to field, but not for the IP address listed as an except.
* set default options (ie: bitfield, header name, etc)
*/
curproxy->options |= PR_O_ORGTO;
free(curproxy->orgto_hdr_name);
curproxy->orgto_hdr_name = strdup(DEF_XORIGINALTO_HDR);
curproxy->orgto_hdr_len = strlen(DEF_XORIGINALTO_HDR);
/* loop to go through arguments - start at 2, since 0+1 = "option" "forwardfor" */
cur_arg = 2;
while (*(args[cur_arg])) {
if (!strcmp(args[cur_arg], "except")) {
/* suboption except - needs additional argument for it */
if (!*(args[cur_arg+1]) || !str2net(args[cur_arg+1], &curproxy->except_to, &curproxy->except_mask_to)) {
Alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
file, linenum, args[0], args[1], args[cur_arg]);
return -1;
}
/* flush useless bits */
curproxy->except_to.s_addr &= curproxy->except_mask_to.s_addr;
cur_arg += 2;
} else if (!strcmp(args[cur_arg], "header")) {
/* suboption header - needs additional argument for it */
if (*(args[cur_arg+1]) == 0) {
Alert("parsing [%s:%d] : '%s %s %s' expects <header_name> as argument.\n",
file, linenum, args[0], args[1], args[cur_arg]);
return -1;
}
free(curproxy->orgto_hdr_name);
curproxy->orgto_hdr_name = strdup(args[cur_arg+1]);
curproxy->orgto_hdr_len = strlen(curproxy->orgto_hdr_name);
cur_arg += 2;
} else {
/* unknown suboption - catchall */
Alert("parsing [%s:%d] : '%s %s' only supports optional values: 'except' and 'header'.\n",
file, linenum, args[0], args[1]);
return -1;
}
} /* end while loop */
}
else {
Alert("parsing [%s:%d] : unknown option '%s'.\n", file, linenum, args[1]);
return -1;

View File

@ -41,6 +41,7 @@
#include <proto/acl.h>
#include <proto/backend.h>
#include <proto/buffers.h>
#include <proto/client.h>
#include <proto/dumpstats.h>
#include <proto/fd.h>
#include <proto/log.h>
@ -2216,7 +2217,52 @@ int http_process_request(struct session *s, struct buffer *req)
}
/*
* 10: add "Connection: close" if needed and not yet set.
* 10: add X-Original-To if either the frontend or the backend
* asks for it.
*/
if ((s->fe->options | s->be->options) & PR_O_ORGTO) {
/* FIXME: don't know if IPv6 can handle that case too. */
if (s->cli_addr.ss_family == AF_INET) {
/* Add an X-Original-To header unless the destination IP is
* in the 'except' network range.
*/
if (!(s->flags & SN_FRT_ADDR_SET))
get_frt_addr(s);
if ((!s->fe->except_mask_to.s_addr ||
(((struct sockaddr_in *)&s->frt_addr)->sin_addr.s_addr & s->fe->except_mask_to.s_addr)
!= s->fe->except_to.s_addr) &&
(!s->be->except_mask_to.s_addr ||
(((struct sockaddr_in *)&s->frt_addr)->sin_addr.s_addr & s->be->except_mask_to.s_addr)
!= s->be->except_to.s_addr)) {
int len;
unsigned char *pn;
pn = (unsigned char *)&((struct sockaddr_in *)&s->frt_addr)->sin_addr;
/* Note: we rely on the backend to get the header name to be used for
* x-original-to, because the header is really meant for the backends.
* However, if the backend did not specify any option, we have to rely
* on the frontend's header name.
*/
if (s->be->orgto_hdr_len) {
len = s->be->orgto_hdr_len;
memcpy(trash, s->be->orgto_hdr_name, len);
} else {
len = s->fe->orgto_hdr_len;
memcpy(trash, s->fe->orgto_hdr_name, len);
}
len += sprintf(trash + len, ": %d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
if (unlikely(http_header_add_tail2(req, &txn->req,
&txn->hdr_idx, trash, len)) < 0)
goto return_bad_req;
}
}
}
/*
* 11: add "Connection: close" if needed and not yet set.
* Note that we do not need to add it in case of HTTP/1.0.
*/
if (!(s->flags & SN_CONN_CLOSED) &&