]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: proxy: move 'forwardfor' option to http_ext
authorAurelien DARRAGON <adarragon@haproxy.com>
Wed, 28 Dec 2022 17:53:05 +0000 (18:53 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 27 Jan 2023 14:18:59 +0000 (15:18 +0100)
Just like forwarded (7239) header, move parsing, logic and management
of 'forwardfor' option into http_ext dedicated class.

We're only doing this to standardize proxy http options management.
Existing behavior remains untouched.

include/haproxy/http_ext-t.h
include/haproxy/http_ext.h
include/haproxy/proxy-t.h
src/cfgparse-listen.c
src/cfgparse.c
src/http_ana.c
src/http_ext.c
src/proxy.c

index b5b22206fb7233ae13f72443cc284f5e84e1c3d6..65a105c21863169098ef5eba574d8b82bf8eb993 100644 (file)
@@ -110,4 +110,14 @@ enum forwarded_header_field {
        FORWARDED_HEADER_ALL    = FORWARDED_HEADER_FOR|FORWARDED_HEADER_BY|FORWARDED_HEADER_HOST|FORWARDED_HEADER_PROTO
 };
 
+enum http_ext_xff_mode {
+       HTTP_XFF_IFNONE = 0, /* set if not already set */
+       HTTP_XFF_ALWAYS = 1 /* always set x-forwarded-for */
+};
+struct http_ext_xff {
+       struct ist      hdr_name;   /* header to use - default: "x-forwarded-for" */
+       struct net_addr except_net; /* don't forward x-forward-for for this address. */
+       uint8_t         mode;
+};
+
 #endif /* !_HAPROXY_HTTPEXT_T_H */
index 481298cc1bb4db7c52e95c699eda782df785090a..7f12830ba1b8dd05384309be18e1ce2effc82d8a 100644 (file)
 int http_validate_7239_header(struct ist hdr, int required_steps, struct forwarded_header_ctx *ctx);
 
 int http_handle_7239_header(struct stream *s, struct channel *req);
+int http_handle_xff_header(struct stream *s, struct channel *req);
 
 void http_ext_7239_clean(struct http_ext_7239 *);
+void http_ext_xff_clean(struct http_ext_xff *);
 
 void http_ext_7239_copy(struct http_ext_7239 *dest, const struct http_ext_7239 *orig);
+void http_ext_xff_copy(struct http_ext_xff *dest, const struct http_ext_xff *orig);
 
 int proxy_http_parse_7239(char **args, int cur_arg, struct proxy *curproxy, const struct proxy *defpx, const char *file, int linenum);
 int proxy_http_compile_7239(struct proxy *curproxy);
+int proxy_http_parse_xff(char **args, int cur_arg, struct proxy *curproxy, const struct proxy *defpx, const char *file, int linenum);
 
 #endif /* !_HAPROXY_HTTPEXT_H */
index 395df2d6a0155928355866aa00951e0fe7b2d4e2..0f682ef45fcf5b833e759ef3768ef36ae8916c72 100644 (file)
@@ -88,12 +88,12 @@ enum PR_SRV_STATE_FILE {
 #define PR_O_PREF_LAST  0x00000020      /* prefer last server */
 #define PR_O_DISPATCH   0x00000040      /* use dispatch mode */
 #define PR_O_FORCED_ID  0x00000080      /* proxy's ID was forced in the configuration */
-#define PR_O_FWDFOR     0x00000100      /* conditionally insert x-forwarded-for with client address */
+#define PR_O_HTTP_XFF   0x00000100      /* conditionally insert x-forwarded-for with client address */
 #define PR_O_IGNORE_PRB 0x00000200      /* ignore empty requests (aborts and timeouts) */
 #define PR_O_NULLNOLOG  0x00000400      /* a connect without request will not be logged */
 #define PR_O_WREQ_BODY  0x00000800      /* always wait for the HTTP request body */
 #define PR_O_HTTP_UPG   0x00001000      /* Contain a "switch-mode http" tcp-request rule */
-#define PR_O_FF_ALWAYS  0x00002000      /* always set x-forwarded-for */
+/* unused: 0x00002000 */
 #define PR_O_PERSIST    0x00004000      /* server persistence stays effective even when server is down */
 #define PR_O_LOGASAP    0x00008000      /* log as soon as possible, without waiting for the stream to complete */
 #define PR_O_ERR_LOGFMT 0x00010000      /* use log-format for connection error message */
@@ -271,6 +271,8 @@ struct error_snapshot {
 struct proxy_http {
        /* forwarded header (RFC 7239) */
        struct http_ext_7239       fwd;
+       /* x-forward-for */
+       struct http_ext_xff        xff;
 };
 
 struct proxy {
@@ -364,9 +366,7 @@ struct proxy {
        unsigned int fe_sps_lim;                /* limit on new sessions per second on the frontend */
        unsigned int fullconn;                  /* #conns on backend above which servers are used at full load */
        unsigned int tot_fe_maxconn;            /* #maxconn of frontends linked to that backend, it is used to compute fullconn */
-       struct net_addr except_xff_net;         /* don't x-forward-for for this address. */
        struct net_addr except_xot_net;         /* don't x-original-to for this address. */
-       struct ist fwdfor_hdr_name;                     /* header to use - default: "x-forwarded-for" */
        struct ist orgto_hdr_name;                      /* header to use - default: "x-original-to" */
        struct ist server_id_hdr_name;                   /* the header to use to send the server id (name) */
        int conn_retries;                       /* maximum number of connect retries */
index 66a0dc5ef293b1e9069929b8d2026abe5a4f953e..112fac3254be897bd42671395fa4e120cf9c6ae6 100644 (file)
@@ -2273,72 +2273,9 @@ stats_error_parsing:
                                goto out;
                }
                else if (strcmp(args[1], "forwardfor") == 0) {
-                       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)
-                        */
-
-                       curproxy->options |= PR_O_FWDFOR | PR_O_FF_ALWAYS;
-
-                       istfree(&curproxy->fwdfor_hdr_name);
-                       curproxy->fwdfor_hdr_name = istdup(ist(DEF_XFORWARDFOR_HDR));
-                       if (!isttest(curproxy->fwdfor_hdr_name))
-                               goto alloc_error;
-                       curproxy->except_xff_net.family = AF_UNSPEC;
-
-                       /* 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") == 0) {
-                                       unsigned char mask;
-                                       int i;
-
-                                       /* suboption except - needs additional argument for it */
-                                       if (*(args[cur_arg+1]) &&
-                                           str2net(args[cur_arg+1], 1, &curproxy->except_xff_net.addr.v4.ip, &curproxy->except_xff_net.addr.v4.mask)) {
-                                               curproxy->except_xff_net.family = AF_INET;
-                                               curproxy->except_xff_net.addr.v4.ip.s_addr &= curproxy->except_xff_net.addr.v4.mask.s_addr;
-                                       }
-                                       else if (*(args[cur_arg+1]) &&
-                                                str62net(args[cur_arg+1], &curproxy->except_xff_net.addr.v6.ip, &mask)) {
-                                               curproxy->except_xff_net.family = AF_INET6;
-                                               len2mask6(mask, &curproxy->except_xff_net.addr.v6.mask);
-                                               for (i = 0; i < 16; i++)
-                                                       curproxy->except_xff_net.addr.v6.ip.s6_addr[i] &= curproxy->except_xff_net.addr.v6.mask.s6_addr[i];
-                                       }
-                                       else {
-                                               ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
-                                                        file, linenum, args[0], args[1], args[cur_arg]);
-                                               err_code |= ERR_ALERT | ERR_FATAL;
-                                               goto out;
-                                       }
-                                       /* flush useless bits */
-                                       cur_arg += 2;
-                               } else if (strcmp(args[cur_arg], "header") == 0) {
-                                       /* suboption header - needs additional argument for it */
-                                       if (*(args[cur_arg+1]) == 0) {
-                                               ha_alert("parsing [%s:%d] : '%s %s %s' expects <header_name> as argument.\n",
-                                                        file, linenum, args[0], args[1], args[cur_arg]);
-                                               err_code |= ERR_ALERT | ERR_FATAL;
-                                               goto out;
-                                       }
-                                       istfree(&curproxy->fwdfor_hdr_name);
-                                       curproxy->fwdfor_hdr_name = istdup(ist(args[cur_arg+1]));
-                                       if (!isttest(curproxy->fwdfor_hdr_name))
-                                               goto alloc_error;
-                                       cur_arg += 2;
-                               } else if (strcmp(args[cur_arg], "if-none") == 0) {
-                                       curproxy->options &= ~PR_O_FF_ALWAYS;
-                                       cur_arg += 1;
-                               } else {
-                                       /* unknown suboption - catchall */
-                                       ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'except', 'header' and 'if-none'.\n",
-                                                file, linenum, args[0], args[1]);
-                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                       goto out;
-                               }
-                       } /* end while loop */
+                       err_code |= proxy_http_parse_xff(args, 0, curproxy, curr_defproxy, file, linenum);
+                       if (err_code & ERR_FATAL)
+                               goto out;
                }
                else if (strcmp(args[1], "originalto") == 0) {
                        int cur_arg;
index 15e22968bd56c680067bc3f1735d746a52c4a37b..358a587528ca35b8ef1e1cc5f9f25631b1b6971a 100644 (file)
@@ -3975,11 +3975,11 @@ out_uri_auth_compat:
                                curproxy->options &= ~PR_O_HTTP_7239;
                        }
 
-                       if (curproxy->options & (PR_O_FWDFOR | PR_O_FF_ALWAYS)) {
+                       if (curproxy->options & PR_O_HTTP_XFF) {
                                ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
                                           "forwardfor", proxy_type_str(curproxy), curproxy->id);
                                err_code |= ERR_WARN;
-                               curproxy->options &= ~(PR_O_FWDFOR | PR_O_FF_ALWAYS);
+                               curproxy->options &= ~PR_O_HTTP_XFF;
                        }
 
                        if (curproxy->options & PR_O_ORGTO) {
index bbc6f4dc1b74dfba7d1234aca1e89be85614b91a..36014718ab26fdc69fd802017cfc515ed6edd052 100644 (file)
@@ -669,60 +669,12 @@ int http_process_request(struct stream *s, struct channel *req, int an_bit)
        }
 
        /*
-        * 9: add X-Forwarded-For if either the frontend or the backend
+        * add X-Forwarded-For if either the frontend or the backend
         * asks for it.
         */
-       if ((sess->fe->options | s->be->options) & PR_O_FWDFOR) {
-               const struct sockaddr_storage *src = sc_src(s->scf);
-               struct http_hdr_ctx ctx = { .blk = NULL };
-               struct ist hdr = isttest(s->be->fwdfor_hdr_name) ? s->be->fwdfor_hdr_name : sess->fe->fwdfor_hdr_name;
-
-               if (!((sess->fe->options | s->be->options) & PR_O_FF_ALWAYS) &&
-                   http_find_header(htx, hdr, &ctx, 0)) {
-                       /* The header is set to be added only if none is present
-                        * and we found it, so don't do anything.
-                        */
-               }
-               else if (src && src->ss_family == AF_INET) {
-                       /* Add an X-Forwarded-For header unless the source IP is
-                        * in the 'except' network range.
-                        */
-                       if (ipcmp2net(src, &sess->fe->except_xff_net) &&
-                           ipcmp2net(src, &s->be->except_xff_net)) {
-                               unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)src)->sin_addr;
-
-                               /* 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.
-                                */
-                               chunk_printf(&trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
-                               if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
-                                       goto return_fail_rewrite;
-                       }
-               }
-               else if (src && src->ss_family == AF_INET6) {
-                       /* Add an X-Forwarded-For header unless the source IP is
-                        * in the 'except' network range.
-                        */
-                       if (ipcmp2net(src, &sess->fe->except_xff_net) &&
-                           ipcmp2net(src, &s->be->except_xff_net)) {
-                               char pn[INET6_ADDRSTRLEN];
-
-                               inet_ntop(AF_INET6,
-                                         (const void *)&((struct sockaddr_in6 *)(src))->sin6_addr,
-                                         pn, sizeof(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.
-                                */
-                               chunk_printf(&trash, "%s", pn);
-                               if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
-                                       goto return_fail_rewrite;
-                       }
-               }
+       if ((sess->fe->options | s->be->options) & PR_O_HTTP_XFF) {
+               if (unlikely(!http_handle_xff_header(s, req)))
+                       goto return_fail_rewrite;
        }
 
        /*
index fcb5a07bc3fbbefbf759a35cd549da6f583c7693..e5c39c2b0ae375c389d18415056c1dd0672fc27d 100644 (file)
@@ -758,6 +758,76 @@ int http_handle_7239_header(struct stream *s, struct channel *req)
        return 1;
 }
 
+/* This function will try to inject x-forwarded-for header if
+ * configured on the frontend or the backend (or both)
+ * Returns 1 for success and 0 for failure
+ */
+int http_handle_xff_header(struct stream *s, struct channel *req)
+{
+       struct session *sess = s->sess;
+       struct htx *htx = htxbuf(&req->buf);
+       const struct sockaddr_storage *src = sc_src(s->scf);
+       struct http_hdr_ctx ctx = { .blk = NULL };
+       struct http_ext_xff *f_xff = ((sess->fe->options & PR_O_HTTP_XFF) ? &sess->fe->http.xff : NULL);
+       struct http_ext_xff *b_xff = ((s->be->options & PR_O_HTTP_XFF) ? &s->be->http.xff : NULL);
+       struct ist hdr;
+
+       /* xff is expected to be enabled on be, or fe, or both */
+       BUG_ON(!f_xff && !b_xff);
+
+       hdr = ((b_xff) ? b_xff->hdr_name : f_xff->hdr_name);
+
+       if (f_xff && f_xff->mode == HTTP_XFF_IFNONE &&
+           b_xff && b_xff->mode == HTTP_XFF_IFNONE &&
+           http_find_header(htx, hdr, &ctx, 0)) {
+               /* The header is set to be added only if none is present
+                * and we found it, so don't do anything.
+                */
+       }
+       else if (src && src->ss_family == AF_INET) {
+               /* Add an X-Forwarded-For header unless the source IP is
+                * in the 'except' network range.
+                */
+               if ((!f_xff || ipcmp2net(src, &f_xff->except_net)) &&
+                   (!b_xff || ipcmp2net(src, &b_xff->except_net))) {
+                       unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)src)->sin_addr;
+
+                       /* 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.
+                        */
+                       chunk_printf(&trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
+                       if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
+                               return 0;
+               }
+       }
+       else if (src && src->ss_family == AF_INET6) {
+               /* Add an X-Forwarded-For header unless the source IP is
+                * in the 'except' network range.
+                */
+               if ((!f_xff || ipcmp2net(src, &f_xff->except_net)) &&
+                   (!b_xff || ipcmp2net(src, &b_xff->except_net))) {
+                       char pn[INET6_ADDRSTRLEN];
+
+                       inet_ntop(AF_INET6,
+                                 (const void *)&((struct sockaddr_in6 *)(src))->sin6_addr,
+                                 pn, sizeof(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.
+                        */
+                       chunk_printf(&trash, "%s", pn);
+                       if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
+                               return 0;
+               }
+       }
+
+       return 1;
+}
+
 /*
  * =========== CONFIG ===========
  * below are helpers to parse http ext options from the config
@@ -1003,6 +1073,83 @@ int proxy_http_compile_7239(struct proxy *curproxy)
        return cfgerr;
 }
 
+/* x-forwarded-for */
+int proxy_http_parse_xff(char **args, int cur_arg,
+                         struct proxy *curproxy, const struct proxy *defpx,
+                         const char *file, int linenum)
+{
+       int err_code = 0;
+
+       /* insert x-forwarded-for field, but not for the IP address listed as an except.
+        * set default options (ie: bitfield, header name, etc)
+        */
+
+       curproxy->options |= PR_O_HTTP_XFF;
+
+       curproxy->http.xff.mode = HTTP_XFF_ALWAYS;
+
+       istfree(&curproxy->http.xff.hdr_name);
+       curproxy->http.xff.hdr_name = istdup(ist(DEF_XFORWARDFOR_HDR));
+       if (!isttest(curproxy->http.xff.hdr_name))
+               return proxy_http_parse_oom(file, linenum);
+       curproxy->http.xff.except_net.family = AF_UNSPEC;
+
+       /* 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") == 0) {
+                       unsigned char mask;
+                       int i;
+
+                       /* suboption except - needs additional argument for it */
+                       if (*(args[cur_arg+1]) &&
+                           str2net(args[cur_arg+1], 1, &curproxy->http.xff.except_net.addr.v4.ip, &curproxy->http.xff.except_net.addr.v4.mask)) {
+                               curproxy->http.xff.except_net.family = AF_INET;
+                               curproxy->http.xff.except_net.addr.v4.ip.s_addr &= curproxy->http.xff.except_net.addr.v4.mask.s_addr;
+                       }
+                       else if (*(args[cur_arg+1]) &&
+                                str62net(args[cur_arg+1], &curproxy->http.xff.except_net.addr.v6.ip, &mask)) {
+                               curproxy->http.xff.except_net.family = AF_INET6;
+                               len2mask6(mask, &curproxy->http.xff.except_net.addr.v6.mask);
+                               for (i = 0; i < 16; i++)
+                                       curproxy->http.xff.except_net.addr.v6.ip.s6_addr[i] &= curproxy->http.xff.except_net.addr.v6.mask.s6_addr[i];
+                       }
+                       else {
+                               ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
+                                        file, linenum, args[0], args[1], args[cur_arg]);
+                               err_code |= ERR_ALERT | ERR_FATAL;
+                               goto out;
+                       }
+                       /* flush useless bits */
+                       cur_arg += 2;
+               } else if (strcmp(args[cur_arg], "header") == 0) {
+                       /* suboption header - needs additional argument for it */
+                       if (*(args[cur_arg+1]) == 0) {
+                               ha_alert("parsing [%s:%d] : '%s %s %s' expects <header_name> as argument.\n",
+                                        file, linenum, args[0], args[1], args[cur_arg]);
+                               err_code |= ERR_ALERT | ERR_FATAL;
+                               goto out;
+                       }
+                       istfree(&curproxy->http.xff.hdr_name);
+                       curproxy->http.xff.hdr_name = istdup(ist(args[cur_arg+1]));
+                       if (!isttest(curproxy->http.xff.hdr_name))
+                               return proxy_http_parse_oom(file, linenum);
+                       cur_arg += 2;
+               } else if (strcmp(args[cur_arg], "if-none") == 0) {
+                       curproxy->http.xff.mode = HTTP_XFF_IFNONE;
+                       cur_arg += 1;
+               } else {
+                       /* unknown suboption - catchall */
+                       ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'except', 'header' and 'if-none'.\n",
+                                file, linenum, args[0], args[1]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+       } /* end while loop */
+ out:
+       return err_code;
+}
+
 /*
  * =========== MGMT ===========
  * below are helpers to manage http ext options
@@ -1029,6 +1176,11 @@ void http_ext_7239_clean(struct http_ext_7239 *clean)
        clean->p_for.np_expr = NULL;
 }
 
+void http_ext_xff_clean(struct http_ext_xff *clean)
+{
+       istfree(&clean->hdr_name);
+}
+
 void http_ext_7239_copy(struct http_ext_7239 *dest, const struct http_ext_7239 *orig)
 {
        if (orig->c_file)
@@ -1057,3 +1209,11 @@ void http_ext_7239_copy(struct http_ext_7239 *dest, const struct http_ext_7239 *
        if (orig->p_for.np_expr_s)
                dest->p_for.np_expr_s = strdup(orig->p_for.np_expr_s);
 }
+
+void http_ext_xff_copy(struct http_ext_xff *dest, const struct http_ext_xff *orig)
+{
+       if (isttest(orig->hdr_name))
+               dest->hdr_name = istdup(orig->hdr_name);
+       dest->mode = orig->mode;
+       dest->except_net = orig->except_net;
+}
index 9166a324ce90cb4a1f5dd4b8e2f1b531470fd413..802d3c790d7b024bd064e6fbccb3693410fa0a72 100644 (file)
@@ -352,9 +352,9 @@ void free_proxy(struct proxy *p)
                pxdf->fct(p);
 
        free(p->desc);
-       istfree(&p->fwdfor_hdr_name);
        istfree(&p->orgto_hdr_name);
        http_ext_7239_clean(&p->http.fwd);
+       http_ext_xff_clean(&p->http.xff);
 
        task_destroy(p->task);
 
@@ -1465,11 +1465,11 @@ void proxy_free_defaults(struct proxy *defproxy)
        istfree(&defproxy->monitor_uri);
        ha_free(&defproxy->defbe.name);
        ha_free(&defproxy->conn_src.iface_name);
-       istfree(&defproxy->fwdfor_hdr_name);
        istfree(&defproxy->orgto_hdr_name);
        istfree(&defproxy->server_id_hdr_name);
 
        http_ext_7239_clean(&defproxy->http.fwd);
+       http_ext_xff_clean(&defproxy->http.xff);
 
        list_for_each_entry_safe(acl, aclb, &defproxy->acl, list) {
                LIST_DELETE(&acl->list);
@@ -1646,14 +1646,13 @@ static int proxy_defproxy_cpy(struct proxy *curproxy, const struct proxy *defpro
        curproxy->options2 = defproxy->options2;
        curproxy->no_options = defproxy->no_options;
        curproxy->no_options2 = defproxy->no_options2;
-       curproxy->except_xff_net = defproxy->except_xff_net;
        curproxy->except_xot_net = defproxy->except_xot_net;
        curproxy->retry_type = defproxy->retry_type;
        curproxy->tcp_req.inspect_delay = defproxy->tcp_req.inspect_delay;
        curproxy->tcp_rep.inspect_delay = defproxy->tcp_rep.inspect_delay;
 
-       if (isttest(defproxy->fwdfor_hdr_name))
-               curproxy->fwdfor_hdr_name = istdup(defproxy->fwdfor_hdr_name);
+       if (defproxy->options & PR_O_HTTP_XFF)
+               http_ext_xff_copy(&curproxy->http.xff, &defproxy->http.xff);
 
        if (isttest(defproxy->orgto_hdr_name))
                curproxy->orgto_hdr_name = istdup(defproxy->orgto_hdr_name);