From af72a1d8ecb702aa2de6b5ed64494aa207ffa4ad Mon Sep 17 00:00:00 2001 From: Ross West Date: Sun, 3 Aug 2008 10:51:45 +0200 Subject: [PATCH] [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] --- doc/configuration.txt | 26 ++++++++++++++++++---- include/common/defaults.h | 3 +++ include/types/proxy.h | 2 ++ src/cfgparse.c | 46 +++++++++++++++++++++++++++++---------- src/proto_http.c | 33 ++++++++++++++++++++++++---- 5 files changed, 91 insertions(+), 19 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index e0e6d7e44..943158a93 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -1836,13 +1836,15 @@ no option forceclose See also : "option httpclose" -option forwardfor [ except ] +option forwardfor [ except ] [ header ] 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 : is an optional argument used to disable this option for sources matching + 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 ] 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 ] 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" diff --git a/include/common/defaults.h b/include/common/defaults.h index da9bdd8c7..05628e176 100644 --- a/include/common/defaults.h +++ b/include/common/defaults.h @@ -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 diff --git a/include/types/proxy.h b/include/types/proxy.h index 415b1a1af..03cb5f638 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -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 */ diff --git a/src/cfgparse.c b/src/cfgparse.c index 09f63a40c..ea73f5375 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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
[/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; + 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 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 { - Alert("parsing [%s:%d] : '%s' only supports optional 'except' address[/mask].\n", - file, linenum, args[0]); + /* 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]); diff --git a/src/proto_http.c b/src/proto_http.c index 5b11cb2b6..9ab5a80c5 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -2214,10 +2214,21 @@ 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, + if (unlikely(http_header_add_tail2(req, &txn->req, &txn->hdr_idx, trash, len)) < 0) goto return_bad_req; } @@ -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;