[MINOR] permit renaming of x-forwarded-for header

Because I needed it in my situation - here's a quick patch to
allow changing of the "x-forwarded-for" header by using a suboption to
"option forwardfor".

Suboption "header XYZ" will set the header from "x-forwarded-for" to "XYZ".

Default is still "x-forwarded-for" if the header value isn't defined.
Also the suboption 'except a.b.c.d/z' still works on the same line.

So it's now: option forwardfor [except a.b.c.d[/z]] [header XYZ]
This commit is contained in:
Ross West 2008-08-03 10:51:45 +02:00 committed by Willy Tarreau
parent dd64f8d394
commit af72a1d8ec
5 changed files with 91 additions and 19 deletions

View File

@ -1836,13 +1836,15 @@ no option forceclose
See also : "option httpclose"
option forwardfor [ except <network> ]
option forwardfor [ except <network> ] [ header <name> ]
Enable insertion of the X-Forwarded-For 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-Forwarded-For"
header name.
Since HAProxy works in reverse-proxy mode, the servers see its IP address as
their client address. This is sometimes annoying when the client's IP address
@ -1851,7 +1853,16 @@ option forwardfor [ except <network> ]
This header contains a value representing the client's IP address. Since this
header is always appended at the end of the existing header list, the server
must be configured to always use the last occurrence of this header only. See
the server's manual to find how to enable use of this standard header.
the server's manual to find how to enable use of this standard header. 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-Forwarded-For". This can be useful where you might already
have a "X-Forwarded-For" header from a different application (eg: stunnel),
and you need preserve it. Also if your backend server doesn't use the
"X-Forwarded-For" header and requires different one (eg: Zeus Web Servers
require "X-Cluster-Client-IP").
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
@ -1862,19 +1873,26 @@ option forwardfor [ except <network> ]
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.
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.
Example :
Examples :
# Public HTTP address also used by stunnel on the same machine
frontend www
mode http
option forwardfor except 127.0.0.1 # stunnel already adds the header
# Those servers want the IP Address in X-Client
backend www
mode http
option forwardfor header X-Client
See also : "option httpclose"

View File

@ -108,6 +108,9 @@
#define DEF_CHECK_REQ "OPTIONS / HTTP/1.0\r\n\r\n"
#define DEF_SMTP_CHECK_REQ "HELO localhost\r\n"
// X-Forwarded-For header default
#define DEF_XFORWARDFOR_HDR "X-Forwarded-For"
/* Default connections limit.
*
* A system limit can be enforced at build time in order to avoid using haproxy

View File

@ -206,6 +206,8 @@ struct proxy {
unsigned int maxconn; /* max # of active sessions 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 */
char *fwdfor_hdr_name; /* header to use - default: "x-forwarded-for" */
int fwdfor_hdr_len; /* length of "x-forwarded-for" header */
unsigned down_trans; /* up-down transitions */
unsigned down_time; /* total time the proxy was down */

View File

@ -1443,25 +1443,49 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
}
}
else if (!strcmp(args[1], "forwardfor")) {
/* insert x-forwarded-for field, but not for the
* IP address listed as an except.
int cur_arg;
/* insert x-forwarded-for field, but not for the IP address listed as an except.
* set default options (ie: bitfield, header name, etc)
*/
if (*(args[2])) {
if (!strcmp(args[2], "except")) {
if (!*args[3] || !str2net(args[3], &curproxy->except_net, &curproxy->except_mask)) {
Alert("parsing [%s:%d] : '%s' only supports optional 'except' address[/mask].\n",
file, linenum, args[0]);
curproxy->options |= PR_O_FWDFOR;
free(curproxy->fwdfor_hdr_name);
curproxy->fwdfor_hdr_name = strdup(DEF_XFORWARDFOR_HDR);
curproxy->fwdfor_hdr_len = strlen(DEF_XFORWARDFOR_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_net, &curproxy->except_mask)) {
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_net.s_addr &= curproxy->except_mask.s_addr;
} else {
Alert("parsing [%s:%d] : '%s' only supports optional 'except' address[/mask].\n",
file, linenum, args[0]);
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->fwdfor_hdr_name);
curproxy->fwdfor_hdr_name = strdup(args[cur_arg+1]);
curproxy->fwdfor_hdr_len = strlen(curproxy->fwdfor_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;
}
curproxy->options |= PR_O_FWDFOR;
} /* end while loop */
}
else {
Alert("parsing [%s:%d] : unknown option '%s'.\n", file, linenum, args[1]);

View File

@ -2214,8 +2214,19 @@ int process_cli(struct session *t)
unsigned char *pn;
pn = (unsigned char *)&((struct sockaddr_in *)&t->cli_addr)->sin_addr;
len = sprintf(trash, "X-Forwarded-For: %d.%d.%d.%d",
pn[0], pn[1], pn[2], pn[3]);
/* Note: we rely on the backend to get the header name to be used for
* x-forwarded-for, 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 (t->be->fwdfor_hdr_len) {
len = t->be->fwdfor_hdr_len;
memcpy(trash, t->be->fwdfor_hdr_name, len);
} else {
len = t->fe->fwdfor_hdr_len;
memcpy(trash, t->fe->fwdfor_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)
@ -2231,7 +2242,21 @@ int process_cli(struct session *t)
inet_ntop(AF_INET6,
(const void *)&((struct sockaddr_in6 *)(&t->cli_addr))->sin6_addr,
pn, sizeof(pn));
len = sprintf(trash, "X-Forwarded-For: %s", pn);
/* Note: we rely on the backend to get the header name to be used for
* x-forwarded-for, 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 (t->be->fwdfor_hdr_len) {
len = t->be->fwdfor_hdr_len;
memcpy(trash, t->be->fwdfor_hdr_name, len);
} else {
len = t->fe->fwdfor_hdr_len;
memcpy(trash, t->fe->fwdfor_hdr_name, len);
}
len += sprintf(trash + len, ": %s", pn);
if (unlikely(http_header_add_tail2(req, &txn->req,
&txn->hdr_idx, trash, len)) < 0)
goto return_bad_req;