Hi, Maik Broemme <mbroe...@plusserver.de> wrote: > Hi, > > I have attached a patch which will add on every http request a new > header 'X-Original-Dst'. 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 'originaldst'. 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. :) >
I have attached a new diff, because the first one has two issues: - The except ip addresses for X-Original-Dst header were matched against 'except_net' instead of 'except_dst' which was wrong. - The except ip addresses for X-Original-Dst header were matched against the netmask of 'except_mask' which is only valid for 'except_net'. I have added a new netmask value 'except_mask_dst' to fix that. > --Maik --Maik
diff -Nur haproxy-1.3.17/doc/architecture.txt haproxy-1.3.17-x-original-dst/doc/architecture.txt --- haproxy-1.3.17/doc/architecture.txt 2009-03-29 15:26:57.000000000 +0200 +++ haproxy-1.3.17-x-original-dst/doc/architecture.txt 2009-04-15 04:34:08.000000000 +0200 @@ -128,6 +128,15 @@ option httpclose option forwardfor + - if the application needs to log the original destination IP, use the + "originaldst" option which will add an "X-Original-Dst" 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 originaldst + The web server will have to be configured to use this header instead. For example, on apache, you can use LogFormat for this : diff -Nur haproxy-1.3.17/doc/configuration.txt haproxy-1.3.17-x-original-dst/doc/configuration.txt --- haproxy-1.3.17/doc/configuration.txt 2009-03-29 15:26:57.000000000 +0200 +++ haproxy-1.3.17-x-original-dst/doc/configuration.txt 2009-04-15 04:30:21.000000000 +0200 @@ -599,6 +599,7 @@ [no] option dontlognull X X X - [no] option forceclose X - X X option forwardfor X X X X +option originaldst X X X X [no] option http_proxy X X X X option httpchk X - X X [no] option httpclose X X X X @@ -2038,6 +2039,65 @@ See also : "option httpclose" +option originaldst [ except <network> ] [ header <name> ] + Enable insertion of the X-Original-Dst 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-Dst" + 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-Dst" 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-Dst". This can be useful where you might already + have a "X-Original-Dst" header from a different application, and you need + preserve it. Also if your backend server doesn't use the "X-Original-Dst" + 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 originaldst except 127.0.0.1 + + # Those servers want the IP Address in X-Client-Dst + backend www + mode http + option originaldst header X-Client-Dst + + See also : "option httpclose" + + option http_proxy no option http_proxy Enable or disable plain HTTP proxy mode diff -Nur haproxy-1.3.17/include/common/defaults.h haproxy-1.3.17-x-original-dst/include/common/defaults.h --- haproxy-1.3.17/include/common/defaults.h 2009-03-29 15:26:57.000000000 +0200 +++ haproxy-1.3.17-x-original-dst/include/common/defaults.h 2009-04-15 04:05:21.000000000 +0200 @@ -130,6 +130,9 @@ // X-Forwarded-For header default #define DEF_XFORWARDFOR_HDR "X-Forwarded-For" +// X-Original-Dst header default +#define DEF_XORIGINALDST_HDR "X-Original-Dst" + /* Default connections limit. * * A system limit can be enforced at build time in order to avoid using haproxy diff -Nur haproxy-1.3.17/include/types/proxy.h haproxy-1.3.17-x-original-dst/include/types/proxy.h --- haproxy-1.3.17/include/types/proxy.h 2009-03-29 15:26:57.000000000 +0200 +++ haproxy-1.3.17-x-original-dst/include/types/proxy.h 2009-04-15 18:40:21.000000000 +0200 @@ -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_ORGDST 0x80000000 /* insert x-original-dst with destination address */ +/* unused: 0x80000000 - now used by PR_O_ORGDST */ /* bits for proxy->options2 */ #define PR_O2_SPLIC_REQ 0x00000001 /* transfer requests using linux kernel's splice() */ @@ -229,8 +230,12 @@ 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_dst; /* don't x-original-dst for this address. */ + struct in_addr except_mask_dst; /* the netmask for except_dst. */ char *fwdfor_hdr_name; /* header to use - default: "x-forwarded-for" */ int fwdfor_hdr_len; /* length of "x-forwarded-for" header */ + char *orgdst_hdr_name; /* header to use - default: "x-original-dst" */ + int orgdst_hdr_len; /* length of "x-original-dst" header */ unsigned down_trans; /* up-down transitions */ unsigned down_time; /* total time the proxy was down */ diff -Nur haproxy-1.3.17/src/cfgparse.c haproxy-1.3.17-x-original-dst/src/cfgparse.c --- haproxy-1.3.17/src/cfgparse.c 2009-03-29 15:26:57.000000000 +0200 +++ haproxy-1.3.17-x-original-dst/src/cfgparse.c 2009-04-15 18:41:17.000000000 +0200 @@ -674,6 +674,7 @@ curproxy->lbprm.algo = defproxy.lbprm.algo; curproxy->except_net = defproxy.except_net; curproxy->except_mask = defproxy.except_mask; + curproxy->except_mask_dst = defproxy.except_mask_dst; if (defproxy.fwdfor_hdr_len) { curproxy->fwdfor_hdr_len = defproxy.fwdfor_hdr_len; @@ -1649,6 +1650,51 @@ } } /* end while loop */ } + else if (!strcmp(args[1], "originaldst")) { + int cur_arg; + + /* insert x-original-dst field, but not for the IP address listed as an except. + * set default options (ie: bitfield, header name, etc) + */ + + curproxy->options |= PR_O_ORGDST; + + free(curproxy->orgdst_hdr_name); + curproxy->orgdst_hdr_name = strdup(DEF_XORIGINALDST_HDR); + curproxy->orgdst_hdr_len = strlen(DEF_XORIGINALDST_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_dst, &curproxy->except_mask_dst)) { + 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_dst.s_addr &= curproxy->except_mask_dst.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->orgdst_hdr_name); + curproxy->orgdst_hdr_name = strdup(args[cur_arg+1]); + curproxy->orgdst_hdr_len = strlen(curproxy->orgdst_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; diff -Nur haproxy-1.3.17/src/proto_http.c haproxy-1.3.17-x-original-dst/src/proto_http.c --- haproxy-1.3.17/src/proto_http.c 2009-03-29 15:26:57.000000000 +0200 +++ haproxy-1.3.17-x-original-dst/src/proto_http.c 2009-04-15 18:41:55.000000000 +0200 @@ -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> @@ -2196,7 +2197,52 @@ } /* - * 10: add "Connection: close" if needed and not yet set. + * 10: add X-Original-Dst if either the frontend or the backend + * asks for it. + */ + if ((s->fe->options | s->be->options) & PR_O_ORGDST) { + + /* FIXME: don't know if IPv6 can handle that case too. */ + if (s->cli_addr.ss_family == AF_INET) { + /* Add an X-Original-Dst header unless the source IP is + * in the 'except' network range. + */ + if (!(s->flags & SN_FRT_ADDR_SET)) + get_frt_addr(s); + + if ((!s->fe->except_mask_dst.s_addr || + (((struct sockaddr_in *)&s->frt_addr)->sin_addr.s_addr & s->fe->except_mask_dst.s_addr) + != s->fe->except_dst.s_addr) && + (!s->be->except_mask_dst.s_addr || + (((struct sockaddr_in *)&s->frt_addr)->sin_addr.s_addr & s->be->except_mask_dst.s_addr) + != s->be->except_dst.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-dst, 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->orgdst_hdr_len) { + len = s->be->orgdst_hdr_len; + memcpy(trash, s->be->orgdst_hdr_name, len); + } else { + len = s->fe->orgdst_hdr_len; + memcpy(trash, s->fe->orgdst_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) &&