From bce70882752cb728c264e7561ff6129101f14c86 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Mon, 7 Sep 2009 11:51:47 +0200 Subject: [PATCH] [MEDIUM] add ability to connect to a server from an IP found in a header Using get_ip_from_hdr2() we can look for occurrence #X or #-X and extract the IP it contains. This is typically designed for use with the X-Forwarded-For header. Using "usesrc hdr_ip(name,occ)", it becomes possible to use the IP address found in , and possibly specify occurrence number , as the source to connect to a server. This is possible both in a server and in a backend's source statement. This is typically used to use the source IP previously set by a upstream proxy. --- doc/configuration.txt | 30 ++++++++++++ include/common/defaults.h | 5 ++ include/proto/proto_http.h | 2 + include/types/proxy.h | 3 ++ include/types/server.h | 3 ++ src/backend.c | 26 +++++++++- src/cfgparse.c | 98 +++++++++++++++++++++++++++++++++++++- src/proto_http.c | 53 +++++++++++++++++++++ 8 files changed, 217 insertions(+), 3 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index c46587f07e..2292674a8c 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -4127,6 +4127,7 @@ server
[:port] [param*] source [:] [usesrc { [:] | client | clientip } ] +source [:] [usesrc { [:] | hdr_ip([,]) } ] source [:] [interface ] Set the source address for outgoing connections May be used in sections : defaults | frontend | listen | backend @@ -4155,6 +4156,29 @@ source [:] [interface ] The default value of zero means the system will select a free port. + is the name of a HTTP header in which to fetch the IP to bind to. + This is the name of a comma-separated header list which can + contain multiple IP addresses. By default, the last occurrence is + used. This is designed to work with the X-Forwarded-For header + and to automatically bind to the the client's IP address as seen + by previous proxy, typically Stunnel. In order to use another + occurrence from the last one, please see the parameter + below. When the header (or occurrence) is not found, no binding + is performed so that the proxy's default IP address is used. Also + keep in mind that the header name is case insensitive, as for any + HTTP header. + + is the occurrence number of a value to be used in a multi-value + header. This is to be used in conjunction with "hdr_ip()", + in order to specificy which occurrence to use for the source IP + address. Positive values indicate a position from the first + occurrence, 1 being the first one. Negative values indicate + positions relative to the last one, -1 being the last one. This + is helpful for situations where an X-Forwarded-For header is set + at the entry point of an infrastructure and must be used several + proxy layers away. When this value is not specified, -1 is + assumed. Passing a zero here disables the feature. + is an optional interface name to which to bind to for outgoing traffic. On systems supporting this features (currently, only Linux), this allows one to bind all traffic to the server to @@ -4230,6 +4254,11 @@ source [:] [interface ] # with Tproxy version 4. source 0.0.0.0 usesrc clientip + backend transparent_http + # Connect to the servers using the client's IP as seen by previous + # proxy. + source 0.0.0.0 usesrc hdr_ip(x-forwarded-for,-1) + See also : the "source" server option in section 5, the Tproxy patches for the Linux kernel on www.balabit.com, the "bind" keyword. @@ -5576,6 +5605,7 @@ slowstart Supported in default-server: Yes source [:[-]] [usesrc { [:] | client | clientip } ] +source [:] [usesrc { [:] | hdr_ip([,]) } ] source [:[-]] [interface ] ... The "source" parameter sets the source address which will be used when connecting to the server. It follows the exact same parameters and principle diff --git a/include/common/defaults.h b/include/common/defaults.h index 7746a98e83..845319dff3 100644 --- a/include/common/defaults.h +++ b/include/common/defaults.h @@ -63,6 +63,11 @@ #define MAX_HTTP_HDR ((BUFSIZE+79)/80) #endif +// max # of headers in history when looking for header #-X +#ifndef MAX_HDR_HISTORY +#define MAX_HDR_HISTORY 10 +#endif + // max # of loops we can perform around a read() which succeeds. // It's very frequent that the system returns a few TCP segments at a time. #ifndef MAX_READ_POLL_LOOPS diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 8213e3b7b8..e7181ec97c 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -92,6 +92,8 @@ void http_return_srv_error(struct session *s, struct stream_interface *si); void http_capture_bad_message(struct error_snapshot *es, struct session *s, struct buffer *buf, struct http_msg *msg, struct proxy *other_end); +unsigned int get_ip_from_hdr2(struct http_msg *msg, const char *hname, int hlen, + struct hdr_idx *idx, int occ); void http_init_txn(struct session *s); void http_end_txn(struct session *s); diff --git a/include/types/proxy.h b/include/types/proxy.h index cc2311781e..c3fd01a519 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -249,6 +249,9 @@ struct proxy { struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */ #if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY) struct sockaddr_in tproxy_addr; /* non-local address we want to bind to for connect() */ + char *bind_hdr_name; /* bind to this header name if defined */ + int bind_hdr_len; /* length of the name of the header above */ + int bind_hdr_occ; /* occurrence number of header above: >0 = from first, <0 = from end, 0=disabled */ #endif int iface_len; /* bind interface name length */ char *iface_name; /* bind interface name or NULL */ diff --git a/include/types/server.h b/include/types/server.h index bf3d6b01e9..00251d8ec1 100644 --- a/include/types/server.h +++ b/include/types/server.h @@ -106,6 +106,9 @@ struct server { struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */ #if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY) struct sockaddr_in tproxy_addr; /* non-local address we want to bind to for connect() */ + char *bind_hdr_name; /* bind to this header name if defined */ + int bind_hdr_len; /* length of the name of the header above */ + int bind_hdr_occ; /* occurrence number of header above: >0 = from first, <0 = from end, 0=disabled */ #endif int iface_len; /* bind interface name length */ char *iface_name; /* bind interface name or NULL */ diff --git a/src/backend.c b/src/backend.c index 845a436bde..ffd3bcab54 100644 --- a/src/backend.c +++ b/src/backend.c @@ -837,8 +837,19 @@ static void assign_tproxy_address(struct session *s) /* FIXME: what can we do if the client connects in IPv6 ? */ s->from_addr = *(struct sockaddr_in *)&s->cli_addr; break; + case SRV_TPROXY_DYN: + if (s->srv->bind_hdr_occ) { + /* bind to the IP in a header */ + s->from_addr.sin_port = 0; + s->from_addr.sin_addr.s_addr = htonl(get_ip_from_hdr2(&s->txn.req, + s->srv->bind_hdr_name, + s->srv->bind_hdr_len, + &s->txn.hdr_idx, + s->srv->bind_hdr_occ)); + } + break; default: - s->from_addr = *(struct sockaddr_in *)0; + memset(&s->from_addr, 0, sizeof(s->from_addr)); } } else if (s->be->options & PR_O_BIND_SRC) { @@ -851,8 +862,19 @@ static void assign_tproxy_address(struct session *s) /* FIXME: what can we do if the client connects in IPv6 ? */ s->from_addr = *(struct sockaddr_in *)&s->cli_addr; break; + case PR_O_TPXY_DYN: + if (s->be->bind_hdr_occ) { + /* bind to the IP in a header */ + s->from_addr.sin_port = 0; + s->from_addr.sin_addr.s_addr = htonl(get_ip_from_hdr2(&s->txn.req, + s->be->bind_hdr_name, + s->be->bind_hdr_len, + &s->txn.hdr_idx, + s->be->bind_hdr_occ)); + } + break; default: - s->from_addr = *(struct sockaddr_in *)0; + memset(&s->from_addr, 0, sizeof(s->from_addr)); } } #endif diff --git a/src/cfgparse.c b/src/cfgparse.c index b647ff2ae1..f3783d4169 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -3451,15 +3451,56 @@ stats_error_parsing: } #endif if (!*args[cur_arg + 1]) { - Alert("parsing [%s:%d] : '%s' expects [:], 'client', or 'clientip' as argument.\n", + Alert("parsing [%s:%d] : '%s' expects [:], 'client', 'clientip', or 'hdr_ip(name,#)' as argument.\n", file, linenum, "usesrc"); err_code |= ERR_ALERT | ERR_FATAL; goto out; } if (!strcmp(args[cur_arg + 1], "client")) { + newsrv->state &= ~SRV_TPROXY_MASK; newsrv->state |= SRV_TPROXY_CLI; } else if (!strcmp(args[cur_arg + 1], "clientip")) { + newsrv->state &= ~SRV_TPROXY_MASK; newsrv->state |= SRV_TPROXY_CIP; + } else if (!strncmp(args[cur_arg + 1], "hdr_ip(", 7)) { + char *name, *end; + + name = args[cur_arg+1] + 7; + while (isspace(*name)) + name++; + + end = name; + while (*end && !isspace(*end) && *end != ',' && *end != ')') + end++; + + newsrv->state &= ~SRV_TPROXY_MASK; + newsrv->state |= SRV_TPROXY_DYN; + newsrv->bind_hdr_name = calloc(1, end - name + 1); + newsrv->bind_hdr_len = end - name; + memcpy(newsrv->bind_hdr_name, name, end - name); + newsrv->bind_hdr_name[end-name] = '\0'; + newsrv->bind_hdr_occ = -1; + + /* now look for an occurrence number */ + while (isspace(*end)) + end++; + if (*end == ',') { + end++; + name = end; + if (*end == '-') + end++; + while (isdigit(*end)) + end++; + newsrv->bind_hdr_occ = strl2ic(name, end-name); + } + + if (newsrv->bind_hdr_occ < -MAX_HDR_HISTORY) { + Alert("parsing [%s:%d] : usesrc hdr_ip(name,num) does not support negative" + " occurrences values smaller than %d.\n", + file, linenum, MAX_HDR_HISTORY); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } } else { struct sockaddr_in *sk = str2sa(args[cur_arg + 1]); if (!sk) { @@ -3725,9 +3766,50 @@ stats_error_parsing: } if (!strcmp(args[cur_arg + 1], "client")) { + curproxy->options &= ~PR_O_TPXY_MASK; curproxy->options |= PR_O_TPXY_CLI; } else if (!strcmp(args[cur_arg + 1], "clientip")) { + curproxy->options &= ~PR_O_TPXY_MASK; curproxy->options |= PR_O_TPXY_CIP; + } else if (!strncmp(args[cur_arg + 1], "hdr_ip(", 7)) { + char *name, *end; + + name = args[cur_arg+1] + 7; + while (isspace(*name)) + name++; + + end = name; + while (*end && !isspace(*end) && *end != ',' && *end != ')') + end++; + + curproxy->options &= ~PR_O_TPXY_MASK; + curproxy->options |= PR_O_TPXY_DYN; + curproxy->bind_hdr_name = calloc(1, end - name + 1); + curproxy->bind_hdr_len = end - name; + memcpy(curproxy->bind_hdr_name, name, end - name); + curproxy->bind_hdr_name[end-name] = '\0'; + curproxy->bind_hdr_occ = -1; + + /* now look for an occurrence number */ + while (isspace(*end)) + end++; + if (*end == ',') { + end++; + name = end; + if (*end == '-') + end++; + while (isdigit(*end)) + end++; + curproxy->bind_hdr_occ = strl2ic(name, end-name); + } + + if (curproxy->bind_hdr_occ < -MAX_HDR_HISTORY) { + Alert("parsing [%s:%d] : usesrc hdr_ip(name,num) does not support negative" + " occurrences values smaller than %d.\n", + file, linenum, MAX_HDR_HISTORY); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } } else { struct sockaddr_in *sk = str2sa(args[cur_arg + 1]); if (!sk) { @@ -5051,6 +5133,13 @@ out_uri_auth_compat: curproxy->options2 &= ~cfg_opts2[optnum].val; } } + + if (curproxy->bind_hdr_occ) { + curproxy->bind_hdr_occ = 0; + Warning("config : %s '%s' : ignoring use of header %s as source IP in non-HTTP mode.\n", + proxy_type_str(curproxy), curproxy->id, curproxy->bind_hdr_name); + err_code |= ERR_WARN; + } } /* @@ -5074,6 +5163,13 @@ out_uri_auth_compat: proxy_type_str(curproxy), curproxy->id); cfgerr++; } + + if (curproxy->mode != PR_MODE_HTTP && newsrv->bind_hdr_occ) { + newsrv->bind_hdr_occ = 0; + Warning("config : %s '%s' : server %s cannot use header %s as source IP in non-HTTP mode.\n", + proxy_type_str(curproxy), curproxy->id, newsrv->id, newsrv->bind_hdr_name); + err_code |= ERR_WARN; + } newsrv = newsrv->next; } diff --git a/src/proto_http.c b/src/proto_http.c index 694e98dc5e..90f984827d 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -6534,6 +6534,59 @@ void http_capture_bad_message(struct error_snapshot *es, struct session *s, es->src = s->cli_addr; } +/* return the IP address pointed to by occurrence of header in + * HTTP message indexed in . If is strictly positive, the + * occurrence number corresponding to this value is returned. If is + * strictly negative, the occurrence number before the end corresponding to + * this value is returned. If is null, any value is returned, so it is + * not recommended to use it that way. Negative occurrences are limited to + * a small value because it is required to keep them in memory while scanning. + * IP address 0.0.0.0 is returned if no match is found. + */ +unsigned int get_ip_from_hdr2(struct http_msg *msg, const char *hname, int hlen, struct hdr_idx *idx, int occ) +{ + struct hdr_ctx ctx; + unsigned int hdr_hist[MAX_HDR_HISTORY]; + unsigned int hist_ptr; + int found = 0; + + ctx.idx = 0; + if (occ >= 0) { + while (http_find_header2(hname, hlen, msg->sol, idx, &ctx)) { + occ--; + if (occ <= 0) { + found = 1; + break; + } + } + if (!found) + return 0; + return inetaddr_host_lim(ctx.line+ctx.val, ctx.line+ctx.val+ctx.vlen); + } + + /* negative occurrence, we scan all the list then walk back */ + if (-occ > MAX_HDR_HISTORY) + return 0; + + hist_ptr = 0; + hdr_hist[hist_ptr] = 0; + while (http_find_header2(hname, hlen, msg->sol, idx, &ctx)) { + hdr_hist[hist_ptr++] = inetaddr_host_lim(ctx.line+ctx.val, ctx.line+ctx.val+ctx.vlen); + if (hist_ptr >= MAX_HDR_HISTORY) + hist_ptr = 0; + found++; + } + if (-occ > found) + return 0; + /* OK now we have the last occurrence in [hist_ptr-1], and we need to + * find occurrence -occ, so we have to check [hist_ptr+occ]. + */ + hist_ptr += occ; + if (hist_ptr >= MAX_HDR_HISTORY) + hist_ptr -= MAX_HDR_HISTORY; + return hdr_hist[hist_ptr]; +} + /* * Print a debug line with a header */ -- 2.39.5