]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] add ability to connect to a server from an IP found in a header
authorWilly Tarreau <w@1wt.eu>
Mon, 7 Sep 2009 09:51:47 +0000 (11:51 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 30 Mar 2010 08:39:43 +0000 (10:39 +0200)
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 <name>, and possibly specify occurrence number <occ>, 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
include/common/defaults.h
include/proto/proto_http.h
include/types/proxy.h
include/types/server.h
src/backend.c
src/cfgparse.c
src/proto_http.c

index c46587f07ecbe9073c76b59f680be1ffb42f8898..2292674a8c334d7d8a9e4e8a827c8407c4c5fb4e 100644 (file)
@@ -4127,6 +4127,7 @@ server <name> <address>[:port] [param*]
 
 
 source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | client | clientip } ]
+source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | hdr_ip(<hdr>[,<occ>]) } ]
 source <addr>[:<port>] [interface <name>]
   Set the source address for outgoing connections
   May be used in sections :   defaults | frontend | listen | backend
@@ -4155,6 +4156,29 @@ source <addr>[:<port>] [interface <name>]
               The default value of zero means the system will select a free
               port.
 
+    <hdr>     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 <occ> 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.
+
+    <occ>     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(<hdr>)",
+              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.
+
     <name>    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 <addr>[:<port>] [interface <name>]
             # 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 <start_time_in_ms>
   Supported in default-server: Yes
 
 source <addr>[:<pl>[-<ph>]] [usesrc { <addr2>[:<port2>] | client | clientip } ]
+source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | hdr_ip(<hdr>[,<occ>]) } ]
 source <addr>[:<pl>[-<ph>]] [interface <name>] ...
   The "source" parameter sets the source address which will be used when
   connecting to the server. It follows the exact same parameters and principle
index 7746a98e83343c753e08366ae73d111cceea0c91..845319dff3c2d781dbaee0742eccd8f223e4cee4 100644 (file)
 #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
index 8213e3b7b8edd7940314363b317448acb89c5320..e7181ec97c15a97d887d8644887018bfc559a8ee 100644 (file)
@@ -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);
index cc2311781ef7a620bb26d352c79cc1276db843e0..c3fd01a519e0e06bc7aac193566cbec00049e61f 100644 (file)
@@ -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 */
index bf3d6b01e926b899fdb9f985b7ce53a7fd84f6f3..00251d8ec1e4bf71083571fed0974d87f8740506 100644 (file)
@@ -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 */
index 845a436bdeca326d9bdebaabacce5ab7bfc8c1e3..ffd3bcab543a84ed6686787fd29bbb96a69519a8 100644 (file)
@@ -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
index b647ff2ae1a2e7ce27142359a149fbecc69f5ac7..f3783d416959fe8434530255106216cd10f0a86d 100644 (file)
@@ -3451,15 +3451,56 @@ stats_error_parsing:
                                                }
 #endif
                                                if (!*args[cur_arg + 1]) {
-                                                       Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], 'client', or 'clientip' as argument.\n",
+                                                       Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], '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;
                }
 
index 694e98dc5e9cd71c2ed5e4b8a6b825268e876b8c..90f984827dc68ca1b88a8226de76f2bd972e7daa 100644 (file)
@@ -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 <occ> of header <hname> in
+ * HTTP message <msg> indexed in <idx>. If <occ> is strictly positive, the
+ * occurrence number corresponding to this value is returned. If <occ> is
+ * strictly negative, the occurrence number before the end corresponding to
+ * this value is returned. If <occ> 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
  */