]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: proxy/http_ext: implement dynamic http_ext
authorAurelien DARRAGON <adarragon@haproxy.com>
Mon, 9 Jan 2023 10:09:03 +0000 (11:09 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 27 Jan 2023 14:18:59 +0000 (15:18 +0100)
proxy http-only options implemented in http_ext were statically stored
within proxy struct.

We're making some changes so that http_ext are now stored in a dynamically
allocated structs.
http_ext related structs are only allocated when needed to save some space
whenever possible, and they are automatically freed upon proxy deletion.

Related PX_O_HTTP{7239,XFF,XOT) option flags were removed because we're now
considering an http_ext option as 'active' if it is allocated (ptr is not NULL)

A few checks (and BUG_ON) were added to make these changes safe because
it adds some (acceptable) complexity to the previous design.

Also, proxy.http was renamed to proxy.http_ext to make things more explicit.

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 ab83b3544167d9f9139746bdbcf4759cd5d459c8..68eb047e2899e2b0ea3e96a4bf75d1a330fe1f5d 100644 (file)
@@ -132,4 +132,18 @@ struct http_ext_xot {
        struct net_addr except_net; /* don't forward x-original-to for this address. */
 };
 
+/* http_ext options */
+struct http_ext {
+       /* forwarded header (RFC 7239) */
+       struct http_ext_7239       *fwd;
+       /* x-forward-for:
+        *   conditionally insert x-forwarded-for with client address
+        */
+       struct http_ext_xff        *xff;
+       /* x-original-to:
+        *  insert x-original-to with destination address
+        */
+       struct http_ext_xot        *xot;
+};
+
 #endif /* !_HAPROXY_HTTPEXT_T_H */
index 63ecafb2b7d21390d5c7bf5367ae5bc4a5b60774..53764a26c0822e8879f35e2452cbf74ca21bddc5 100644 (file)
@@ -33,17 +33,26 @@ int http_handle_7239_header(struct stream *s, struct channel *req);
 int http_handle_xff_header(struct stream *s, struct channel *req);
 int http_handle_xot_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_xot_clean(struct http_ext_xot *);
-
-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);
-void http_ext_xot_copy(struct http_ext_xot *dest, const struct http_ext_xot *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);
 int proxy_http_parse_xot(char **args, int cur_arg, struct proxy *curproxy, const struct proxy *defpx, const char *file, int linenum);
 
+int http_ext_7239_prepare(struct proxy *cur);
+int http_ext_xff_prepare(struct proxy *cur);
+int http_ext_xot_prepare(struct proxy *cur);
+
+void http_ext_7239_dup(const struct proxy *def, struct proxy *cpy);
+void http_ext_xff_dup(const struct proxy *def, struct proxy *cpy);
+void http_ext_xot_dup(const struct proxy *def, struct proxy *cpy);
+
+void http_ext_7239_clean(struct proxy *cur);
+void http_ext_xff_clean(struct proxy *cur);
+void http_ext_xot_clean(struct proxy *cur);
+
+int http_ext_prepare(struct proxy *cur);
+void http_ext_dup(const struct proxy *def, struct proxy *cpy);
+void http_ext_clean(struct proxy *cur);
+void http_ext_softclean(struct proxy *cur);
+
 #endif /* !_HAPROXY_HTTPEXT_H */
index 8391f9fbc39223c9d36963a034f3dce13fbf99e1..3db2d3e18856abe60837772615ad54ad741602fe 100644 (file)
@@ -88,7 +88,7 @@ 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_HTTP_XFF   0x00000100      /* conditionally insert x-forwarded-for with client address */
+/* unused: 0x00000100 */
 #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 */
@@ -101,7 +101,7 @@ enum PR_SRV_STATE_FILE {
 #define PR_O_TCP_CLI_KA 0x00040000      /* enable TCP keep-alive on client-side streams */
 #define PR_O_TCP_SRV_KA 0x00080000      /* enable TCP keep-alive on server-side streams */
 #define PR_O_USE_ALL_BK 0x00100000      /* load-balance between backup servers */
-#define PR_O_HTTP_7239  0x00200000      /* insert 7239 forwarded header */
+/* unused: 0x00200000 */
 #define PR_O_TCP_NOLING 0x00400000      /* disable lingering on client and server connections */
 #define PR_O_ABRT_CLOSE 0x00800000      /* immediately abort request when client closes */
 
@@ -115,7 +115,7 @@ enum PR_SRV_STATE_FILE {
 #define PR_O_CONTSTATS 0x10000000      /* continuous counters */
 /* unused: 0x20000000 */
 #define PR_O_DISABLE404 0x40000000      /* Disable a server on a 404 response to a health-check */
-#define PR_O_HTTP_XOT   0x80000000      /* insert x-original-to with destination address */
+/* unused: 0x80000000 */
 
 /* bits for proxy->options2 */
 #define PR_O2_SPLIC_REQ        0x00000001      /* transfer requests using linux kernel's splice() */
@@ -267,16 +267,6 @@ struct error_snapshot {
        char buf[VAR_ARRAY];                    /* copy of the beginning of the message for bufsize bytes */
 };
 
-/* http options */
-struct proxy_http {
-       /* forwarded header (RFC 7239) */
-       struct http_ext_7239       fwd;
-       /* x-forward-for */
-       struct http_ext_xff        xff;
-       /* x-original-to */
-       struct http_ext_xot        xot;
-};
-
 struct proxy {
        enum obj_type obj_type;                 /* object type == OBJ_TYPE_PROXY */
        char flags;                             /* bit field PR_FL_* */
@@ -448,7 +438,7 @@ struct proxy {
                char *elfs_file;
                int elfs_line;
        } conf;                                 /* config information */
-       struct proxy_http http;                 /* http only options */
+       struct http_ext *http_ext;              /* http ext options */
        struct eb_root used_server_addr;        /* list of server addresses in use */
        void *parent;                           /* parent of the proxy when applicable */
        struct comp *comp;                      /* http compression */
index 20965c877408d4938dca85b9334990c1d8653766..485b0b36c3d6b306c20b144619b7a9ed4e2c7487 100644 (file)
@@ -2038,7 +2038,8 @@ stats_error_parsing:
                                goto out;
                        }
                        else if (kwm == KWM_NO) {
-                               curproxy->options &= ~PR_O_HTTP_7239;
+                               if (curproxy->http_ext)
+                                       http_ext_7239_clean(curproxy);
                                goto out;
                        }
                }
index ecb75a73aff20306322d849021ede23191da6ebd..c207c6e01a787468e222eb6796c674f6d880b4a0 100644 (file)
@@ -3670,8 +3670,9 @@ out_uri_auth_compat:
                        else
                                curproxy->http_needed |= !!(curproxy->lbprm.expr->fetch->use & SMP_USE_HTTP_ANY);
                }
+
                /* option "forwarded" may need to compile its expressions */
-               if ((curproxy->mode == PR_MODE_HTTP) && curproxy->options & PR_O_HTTP_7239)
+               if ((curproxy->mode == PR_MODE_HTTP) && curproxy->http_ext && curproxy->http_ext->fwd)
                        cfgerr += proxy_http_compile_7239(curproxy);
 
                /* only now we can check if some args remain unresolved.
@@ -3968,25 +3969,26 @@ out_uri_auth_compat:
                                err_code |= ERR_WARN;
                        }
 
-                       if (curproxy->options & PR_O_HTTP_7239) {
-                               ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
-                                          "forwarded", proxy_type_str(curproxy), curproxy->id);
-                               err_code |= ERR_WARN;
-                               curproxy->options &= ~PR_O_HTTP_7239;
-                       }
-
-                       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_HTTP_XFF;
-                       }
-
-                       if (curproxy->options & PR_O_HTTP_XOT) {
-                               ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
-                                          "originalto", proxy_type_str(curproxy), curproxy->id);
-                               err_code |= ERR_WARN;
-                               curproxy->options &= ~PR_O_HTTP_XOT;
+                       if (curproxy->http_ext) {
+                               /* consistency checks for http_ext */
+                               if (curproxy->http_ext->fwd) {
+                                       ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
+                                                  "forwarded", proxy_type_str(curproxy), curproxy->id);
+                                       err_code |= ERR_WARN;
+                                       http_ext_7239_clean(curproxy);
+                               }
+                               if (curproxy->http_ext->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;
+                                       http_ext_xff_clean(curproxy);
+                               }
+                               if (curproxy->http_ext->xot) {
+                                       ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
+                                                  "originalto", proxy_type_str(curproxy), curproxy->id);
+                                       err_code |= ERR_WARN;
+                                       http_ext_xot_clean(curproxy);
+                               }
                        }
 
                        for (optnum = 0; cfg_opts[optnum].name; optnum++) {
@@ -4221,6 +4223,8 @@ out_uri_auth_compat:
                                rules->flags = 0;
                        }
                }
+               /* http_ext post init early cleanup */
+               http_ext_softclean(curproxy);
        }
 
        /*
index fe5044e068f2d0dbec79550e5bffacb6f87d1114..4f1decdbbd4c7b5c77fcefad76adfc62f602e8f5 100644 (file)
@@ -662,29 +662,13 @@ int http_process_request(struct stream *s, struct channel *req, int an_bit)
                                goto return_fail_rewrite;
        }
 
-       /* add forwarded header (RFC 7239) (ignored for frontends) */
-       if (s->be->options & PR_O_HTTP_7239) {
-               if (unlikely(!http_handle_7239_header(s, req)))
-                       goto return_fail_rewrite;
-       }
-
-       /*
-        * add X-Forwarded-For if either the frontend or the backend
-        * asks for it.
-        */
-       if ((sess->fe->options | s->be->options) & PR_O_HTTP_XFF) {
-               if (unlikely(!http_handle_xff_header(s, req)))
-                       goto return_fail_rewrite;
-       }
-
-       /*
-        * add X-Original-To if either the frontend or the backend
-        * asks for it.
-        */
-       if ((sess->fe->options | s->be->options) & PR_O_HTTP_XOT) {
-               if (unlikely(!http_handle_xot_header(s, req)))
-                       goto return_fail_rewrite;
-       }
+       /* handle http extensions (if configured) */
+       if (unlikely(!http_handle_7239_header(s, req)))
+               goto return_fail_rewrite;
+       if (unlikely(!http_handle_xff_header(s, req)))
+               goto return_fail_rewrite;
+       if (unlikely(!http_handle_xot_header(s, req)))
+               goto return_fail_rewrite;
 
        /* Filter the request headers if there are filters attached to the
         * stream.
index f2e3781c5994485647eaf4d779d8fe7671fbba56..0dcafc5dc63425246ab8d61009c90053f8ce01f7 100644 (file)
@@ -611,7 +611,7 @@ static inline void http_build_7239_header_node(struct buffer *out,
        if (forby->np_mode)
                chunk_appendf(out, "\"");
        offset_save = out->data;
-       http_build_7239_header_node(out, s, curproxy, addr, &curproxy->http.fwd.p_by);
+       http_build_7239_header_nodename(out, s, curproxy, addr, forby);
        if (offset_save == out->data) {
                /* could not build nodename, either because some
                 * data is not available or user is providing bad input
@@ -621,7 +621,7 @@ static inline void http_build_7239_header_node(struct buffer *out,
        if (forby->np_mode) {
                chunk_appendf(out, ":");
                offset_save = out->data;
-               http_build_7239_header_nodeport(out, s, curproxy, addr, &curproxy->http.fwd.p_by);
+               http_build_7239_header_nodeport(out, s, curproxy, addr, forby);
                if (offset_save == out->data) {
                        /* could not build nodeport, either because some data is
                         * not available or user is providing bad input
@@ -682,31 +682,30 @@ static int http_build_7239_header(struct buffer *out,
 {
        struct connection *cli_conn = objt_conn(strm_sess(s)->origin);
 
-       if (curproxy->http.fwd.p_proto) {
+       if (curproxy->http_ext->fwd->p_proto) {
                chunk_appendf(out, "%sproto=%s", ((out->data) ? ";" : ""),
                        ((conn_is_ssl(cli_conn)) ? "https" : "http"));
        }
-       if (curproxy->http.fwd.p_host.mode) {
+       if (curproxy->http_ext->fwd->p_host.mode) {
                /* always add quotes for host parameter to make output compliancy checks simpler */
                chunk_appendf(out, "%shost=\"", ((out->data) ? ";" : ""));
                /* ignore return value for now, but could be useful some day */
-               http_build_7239_header_host(out, s, curproxy, htx,
-                                           &curproxy->http.fwd.p_host);
+               http_build_7239_header_host(out, s, curproxy, htx, &curproxy->http_ext->fwd->p_host);
                chunk_appendf(out, "\"");
        }
 
-       if (curproxy->http.fwd.p_by.nn_mode) {
+       if (curproxy->http_ext->fwd->p_by.nn_mode) {
                const struct sockaddr_storage *dst = sc_dst(s->scf);
 
                chunk_appendf(out, "%sby=", ((out->data) ? ";" : ""));
-               http_build_7239_header_node(out, s, curproxy, dst, &curproxy->http.fwd.p_by);
+               http_build_7239_header_node(out, s, curproxy, dst, &curproxy->http_ext->fwd->p_by);
        }
 
-       if (curproxy->http.fwd.p_for.nn_mode) {
+       if (curproxy->http_ext->fwd->p_for.nn_mode) {
                const struct sockaddr_storage *src = sc_src(s->scf);
 
                chunk_appendf(out, "%sfor=", ((out->data) ? ";" : ""));
-               http_build_7239_header_node(out, s, curproxy, src, &curproxy->http.fwd.p_for);
+               http_build_7239_header_node(out, s, curproxy, src, &curproxy->http_ext->fwd->p_for);
        }
        if (unlikely(out->data == out->size)) {
                /* not enough space in buffer, error */
@@ -715,177 +714,196 @@ static int http_build_7239_header(struct buffer *out,
        return 1;
 }
 
-/* This function will try to inject 7239 forwarded header
+/* This function will try to inject RFC 7239 forwarded header if
+ * configured on the backend (ignored for frontends).
+ * Will do nothing if the option is not enabled on the proxy.
  * Returns 1 for success and 0 for failure
  */
 int http_handle_7239_header(struct stream *s, struct channel *req)
 {
-       struct htx *htx = htxbuf(&req->buf);
        struct proxy *curproxy = s->be; /* ignore frontend */
-       int validate = 1;
-       struct http_hdr_ctx find = { .blk = NULL };
-       struct http_hdr_ctx last = { .blk = NULL};
-       struct ist hdr = ist("forwarded");
 
-       BUG_ON(!(curproxy->options & PR_O_HTTP_7239)); /* should not happen */
+       if (curproxy->http_ext && curproxy->http_ext->fwd) {
+               struct htx *htx = htxbuf(&req->buf);
+               int validate = 1;
+               struct http_hdr_ctx find = { .blk = NULL };
+               struct http_hdr_ctx last = { .blk = NULL};
+               struct ist hdr = ist("forwarded");
 
-       /* ok, let's build forwarded header */
-       chunk_reset(&trash);
-       if (unlikely(!http_build_7239_header(&trash, s, curproxy, htx)))
-               return 0; /* error when building header (bad user conf or memory error) */
+               /* ok, let's build forwarded header */
+               chunk_reset(&trash);
+               if (unlikely(!http_build_7239_header(&trash, s, curproxy, htx)))
+                       return 0; /* error when building header (bad user conf or memory error) */
 
-       /* validate existing forwarded header (including multiple values),
-        * hard stop if error is encountered
-        */
-       while (http_find_header(htx, hdr, &find, 0)) {
-               /* validate current header chunk */
-               if (!http_validate_7239_header(find.value, FORWARDED_HEADER_ALL, NULL)) {
-                       /* at least one error, existing forwarded header not OK, add our own
-                        * forwarded header, so that it can be trusted
-                        */
-                       validate = 0;
-                       break;
+               /* validate existing forwarded header (including multiple values),
+                * hard stop if error is encountered
+                */
+               while (http_find_header(htx, hdr, &find, 0)) {
+                       /* validate current header chunk */
+                       if (!http_validate_7239_header(find.value, FORWARDED_HEADER_ALL, NULL)) {
+                               /* at least one error, existing forwarded header not OK, add our own
+                                * forwarded header, so that it can be trusted
+                                */
+                               validate = 0;
+                               break;
+                       }
+                       last = find;
+               }
+               /* no errors, append our data at the end of existing header */
+               if (last.blk && validate) {
+                       if (unlikely(!http_append_header_value(htx, &last, ist2(trash.area, trash.data))))
+                               return 0; /* htx error */
+               }
+               else {
+                       if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
+                               return 0; /* htx error */
                }
-               last = find;
-       }
-       /* no errors, append our data at the end of existing header */
-       if (last.blk && validate) {
-               if (unlikely(!http_append_header_value(htx, &last, ist2(trash.area, trash.data))))
-                       return 0; /* htx error */
-       }
-       else {
-               if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
-                       return 0; /* htx error */
        }
        return 1;
 }
 
-/* This function will try to inject x-forwarded-for header if
- * configured on the frontend or the backend (or both)
+/*
+ * add X-Forwarded-For if either the frontend or the backend
+ * asks for it.
  * 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);
+       struct http_ext_xff *f_xff = NULL;
+       struct http_ext_xff *b_xff = NULL;
 
-       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.
-                */
+       if (sess->fe->http_ext && sess->fe->http_ext->xff) {
+               /* frontend */
+               f_xff = sess->fe->http_ext->xff;
        }
-       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.
+       if (s->be->http_ext && s->be->http_ext->xff) {
+               /* backend */
+               b_xff = s->be->http_ext->xff;
+       }
+
+       if (f_xff || b_xff) {
+               struct htx *htx = htxbuf(&req->buf);
+               const struct sockaddr_storage *src = sc_src(s->scf);
+               struct http_hdr_ctx ctx = { .blk = NULL };
+               struct ist 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.
                         */
-                       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.
+               else if (src && src->ss_family == AF_INET) {
+                       /* Add an X-Forwarded-For header unless the source IP is
+                        * in the 'except' network range.
                         */
-                       chunk_printf(&trash, "%s", pn);
-                       if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
-                               return 0;
+                       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;
 }
 
-/* This function will try to inject x-original-to header if
- * configured on the frontend or the backend (or both)
+/*
+ * add X-Original-To if either the frontend or the backend
+ * asks for it.
  * Returns 1 for success and 0 for failure
  */
 int http_handle_xot_header(struct stream *s, struct channel *req)
 {
        struct session *sess = s->sess;
-       struct htx *htx = htxbuf(&req->buf);
-       const struct sockaddr_storage *dst = sc_dst(s->scf);
-       struct http_ext_xot *f_xot = ((sess->fe->options & PR_O_HTTP_XOT) ? &sess->fe->http.xot : NULL);
-       struct http_ext_xot *b_xot = ((s->be->options & PR_O_HTTP_XOT) ? &s->be->http.xot : NULL);
-       struct ist hdr;
+       struct http_ext_xot *f_xot = NULL;
+       struct http_ext_xot *b_xot = NULL;
 
-       /* xot is expected to be enabled on be, or fe, or both */
-       BUG_ON(!f_xot && !b_xot);
+       if (sess->fe->http_ext && sess->fe->http_ext->xot) {
+               /* frontend */
+               f_xot = sess->fe->http_ext->xot;
+       }
+       if (s->be->http_ext && s->be->http_ext->xot) {
+               /* backend */
+               BUG_ON(!s->be->http_ext);
+               b_xot = s->be->http_ext->xot;
+       }
 
-       hdr = ((b_xot) ? b_xot->hdr_name : f_xot->hdr_name);
+       if (f_xot || b_xot) {
+               struct htx *htx = htxbuf(&req->buf);
+               const struct sockaddr_storage *dst = sc_dst(s->scf);
+               struct ist hdr = ((b_xot) ? b_xot->hdr_name : f_xot->hdr_name);
 
-       if (dst && dst->ss_family == AF_INET) {
-               /* Add an X-Original-To header unless the destination IP is
-                * in the 'except' network range.
-                */
-               if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) &&
-                   (!b_xot || ipcmp2net(dst, &b_xot->except_net))) {
-                       unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)dst)->sin_addr;
-
-                       /* Note: we rely on the backend to get the header name to be used for
-                        * x-original-to, 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 (dst && dst->ss_family == AF_INET) {
+                       /* Add an X-Original-To header unless the destination IP is
+                        * in the 'except' network range.
                         */
-                       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;
+                       if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) &&
+                           (!b_xot || ipcmp2net(dst, &b_xot->except_net))) {
+                               unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)dst)->sin_addr;
+
+                               /* Note: we rely on the backend to get the header name to be used for
+                                * x-original-to, 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 (dst && dst->ss_family == AF_INET6) {
-               /* Add an X-Original-To header unless the source IP is
-                * in the 'except' network range.
-                */
-               if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) &&
-                   (!b_xot || ipcmp2net(dst, &b_xot->except_net))) {
-                       char pn[INET6_ADDRSTRLEN];
-
-                       inet_ntop(AF_INET6,
-                                 (const void *)&((struct sockaddr_in6 *)dst)->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.
+               else if (dst && dst->ss_family == AF_INET6) {
+                       /* Add an X-Original-To header unless the source IP is
+                        * in the 'except' network range.
                         */
-                       chunk_printf(&trash, "%s", pn);
-                       if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
-                               return 0;
+                       if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) &&
+                           (!b_xot || ipcmp2net(dst, &b_xot->except_net))) {
+                               char pn[INET6_ADDRSTRLEN];
+
+                               inet_ntop(AF_INET6,
+                                         (const void *)&((struct sockaddr_in6 *)dst)->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;
@@ -930,6 +948,7 @@ int proxy_http_parse_7239(char **args, int cur_arg,
                           struct proxy *curproxy, const struct proxy *defpx,
                           const char *file, int linenum)
 {
+       struct http_ext_7239 *fwd;
        int err_code = 0;
 
        if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, "option forwarded", NULL)) {
@@ -938,73 +957,77 @@ int proxy_http_parse_7239(char **args, int cur_arg,
                goto out;
        }
 
-       curproxy->options |= PR_O_HTTP_7239;
-       curproxy->http.fwd.p_proto = 0;
-       curproxy->http.fwd.p_host.mode = 0;
-       curproxy->http.fwd.p_for.nn_mode = 0;
-       curproxy->http.fwd.p_for.np_mode = 0;
-       curproxy->http.fwd.p_by.nn_mode = 0;
-       curproxy->http.fwd.p_by.np_mode = 0;
-       ha_free(&curproxy->http.fwd.c_file);
-       curproxy->http.fwd.c_file = strdup(file);
-       curproxy->http.fwd.c_line = linenum;
+       if (!http_ext_7239_prepare(curproxy))
+               return proxy_http_parse_oom(file, linenum);
+
+       fwd = curproxy->http_ext->fwd;
+
+       fwd->p_proto = 0;
+       fwd->p_host.mode = 0;
+       fwd->p_for.nn_mode = 0;
+       fwd->p_for.np_mode = 0;
+       fwd->p_by.nn_mode = 0;
+       fwd->p_by.np_mode = 0;
+       ha_free(&fwd->c_file);
+       fwd->c_file = strdup(file);
+       fwd->c_line = linenum;
 
        /* start at 2, since 0+1 = "option" "forwarded" */
        cur_arg = 2;
        if (!*(args[cur_arg])) {
                /* no optional argument provided, use default settings */
-               curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_ORIG; /* enable for and mimic xff */
-               curproxy->http.fwd.p_proto = 1; /* enable proto */
+               fwd->p_for.nn_mode = HTTP_7239_FORBY_ORIG; /* enable for and mimic xff */
+               fwd->p_proto = 1; /* enable proto */
                goto out;
        }
        /* loop to go through optional arguments */
        while (*(args[cur_arg])) {
                if (strcmp(args[cur_arg], "proto") == 0) {
-                       curproxy->http.fwd.p_proto = 1;
+                       fwd->p_proto = 1;
                        cur_arg += 1;
                } else if (strcmp(args[cur_arg], "host") == 0) {
-                       curproxy->http.fwd.p_host.mode = HTTP_7239_HOST_ORIG;
+                       fwd->p_host.mode = HTTP_7239_HOST_ORIG;
                        cur_arg += 1;
                } else if (strcmp(args[cur_arg], "host-expr") == 0) {
-                       curproxy->http.fwd.p_host.mode = HTTP_7239_HOST_SMP;
+                       fwd->p_host.mode = HTTP_7239_HOST_SMP;
                        err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
-                                                               &curproxy->http.fwd.p_host.expr_s);
+                                                               &fwd->p_host.expr_s);
                        if (err_code & ERR_FATAL)
                                goto out;
                } else if (strcmp(args[cur_arg], "by") == 0) {
-                       curproxy->http.fwd.p_by.nn_mode = HTTP_7239_FORBY_ORIG;
+                       fwd->p_by.nn_mode = HTTP_7239_FORBY_ORIG;
                        cur_arg += 1;
                } else if (strcmp(args[cur_arg], "by-expr") == 0) {
-                       curproxy->http.fwd.p_by.nn_mode = HTTP_7239_FORBY_SMP;
+                       fwd->p_by.nn_mode = HTTP_7239_FORBY_SMP;
                        err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
-                                                               &curproxy->http.fwd.p_by.nn_expr_s);
+                                                               &fwd->p_by.nn_expr_s);
                        if (err_code & ERR_FATAL)
                                goto out;
                } else if (strcmp(args[cur_arg], "for") == 0) {
-                       curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_ORIG;
+                       fwd->p_for.nn_mode = HTTP_7239_FORBY_ORIG;
                        cur_arg += 1;
                } else if (strcmp(args[cur_arg], "for-expr") == 0) {
-                       curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_SMP;
+                       fwd->p_for.nn_mode = HTTP_7239_FORBY_SMP;
                        err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
-                                                               &curproxy->http.fwd.p_for.nn_expr_s);
+                                                               &fwd->p_for.nn_expr_s);
                        if (err_code & ERR_FATAL)
                                goto out;
                } else if (strcmp(args[cur_arg], "by_port") == 0) {
-                       curproxy->http.fwd.p_by.np_mode = HTTP_7239_FORBY_ORIG;
+                       fwd->p_by.np_mode = HTTP_7239_FORBY_ORIG;
                        cur_arg += 1;
                } else if (strcmp(args[cur_arg], "by_port-expr") == 0) {
-                       curproxy->http.fwd.p_by.np_mode = HTTP_7239_FORBY_SMP;
+                       fwd->p_by.np_mode = HTTP_7239_FORBY_SMP;
                        err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
-                                                               &curproxy->http.fwd.p_by.np_expr_s);
+                                                               &fwd->p_by.np_expr_s);
                        if (err_code & ERR_FATAL)
                                goto out;
                } else if (strcmp(args[cur_arg], "for_port") == 0) {
-                       curproxy->http.fwd.p_for.np_mode = HTTP_7239_FORBY_ORIG;
+                       fwd->p_for.np_mode = HTTP_7239_FORBY_ORIG;
                        cur_arg += 1;
                } else if (strcmp(args[cur_arg], "for_port-expr") == 0) {
-                       curproxy->http.fwd.p_for.np_mode = HTTP_7239_FORBY_SMP;
+                       fwd->p_for.np_mode = HTTP_7239_FORBY_SMP;
                        err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
-                                                               &curproxy->http.fwd.p_for.np_expr_s);
+                                                               &fwd->p_for.np_expr_s);
                        if (err_code & ERR_FATAL)
                                goto out;
                } else {
@@ -1019,24 +1042,24 @@ int proxy_http_parse_7239(char **args, int cur_arg,
        } /* end while loop */
 
        /* consistency check */
-       if (curproxy->http.fwd.p_by.np_mode &&
-           !curproxy->http.fwd.p_by.nn_mode) {
-               curproxy->http.fwd.p_by.np_mode = 0;
-               ha_free(&curproxy->http.fwd.p_by.np_expr_s);
+       if (fwd->p_by.np_mode &&
+           !fwd->p_by.nn_mode) {
+               fwd->p_by.np_mode = 0;
+               ha_free(&fwd->p_by.np_expr_s);
                ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'by' "
                           "and 'by-expr' are unset\n",
                           file, linenum, args[0], args[1],
-                          ((curproxy->http.fwd.p_by.np_mode == HTTP_7239_FORBY_ORIG) ? "by_port" : "by_port-expr"));
+                          ((fwd->p_by.np_mode == HTTP_7239_FORBY_ORIG) ? "by_port" : "by_port-expr"));
                err_code |= ERR_WARN;
        }
-       if (curproxy->http.fwd.p_for.np_mode &&
-           !curproxy->http.fwd.p_for.nn_mode) {
-               curproxy->http.fwd.p_for.np_mode = 0;
-               ha_free(&curproxy->http.fwd.p_for.np_expr_s);
+       if (fwd->p_for.np_mode &&
+           !fwd->p_for.nn_mode) {
+               fwd->p_for.np_mode = 0;
+               ha_free(&fwd->p_for.np_expr_s);
                ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'for' "
                           "and 'for-expr' are unset\n",
                           file, linenum, args[0], args[1],
-                          ((curproxy->http.fwd.p_for.np_mode == HTTP_7239_FORBY_ORIG) ? "for_port" : "for_port-expr"));
+                          ((fwd->p_for.np_mode == HTTP_7239_FORBY_ORIG) ? "for_port" : "for_port-expr"));
                err_code |= ERR_WARN;
        }
 
@@ -1049,21 +1072,22 @@ int proxy_http_parse_7239(char **args, int cur_arg,
  */
 int proxy_http_compile_7239(struct proxy *curproxy)
 {
+       struct http_ext_7239 *fwd;
        int cfgerr = 0;
        int loop;
 
-       BUG_ON(!(curproxy->options & PR_O_HTTP_7239)); /* should not happen */
        if (!(curproxy->cap & PR_CAP_BE)) {
-               /* no backend cap: not supported (ie: frontend)
-                * Moreover, 7239 settings are only inherited from default
-                * if proxy is backend capable.. going further would result in
-                * undefined behavior */
+               /* no backend cap: not supported (ie: frontend) */
                goto out;
        }
 
+       /* should not happen (test should be performed after BE cap test) */
+       BUG_ON(!curproxy->http_ext || !curproxy->http_ext->fwd);
+
        curproxy->conf.args.ctx = ARGC_OPT; /* option */
-       curproxy->conf.args.file = curproxy->http.fwd.c_file;
-       curproxy->conf.args.line = curproxy->http.fwd.c_line;
+       curproxy->conf.args.file = curproxy->http_ext->fwd->c_file;
+       curproxy->conf.args.line = curproxy->http_ext->fwd->c_line;
+       fwd = curproxy->http_ext->fwd;
 
        /* it is important that we keep iterating on error to make sure
         * all fwd config fields are in the same state (post-parsing state)
@@ -1079,38 +1103,33 @@ int proxy_http_compile_7239(struct proxy *curproxy)
                switch (loop) {
                        case 0:
                                /* host */
-                               expr_str = &curproxy->http.fwd.p_host.expr_s;
-                               expr = &curproxy->http.fwd.p_host.expr;
-                               smp = (curproxy->http.fwd.p_host.mode ==
-                                      HTTP_7239_HOST_SMP);
+                               expr_str = &fwd->p_host.expr_s;
+                               expr = &fwd->p_host.expr;
+                               smp = (fwd->p_host.mode == HTTP_7239_HOST_SMP);
                                break;
                        case 1:
                                /* by->node */
-                               expr_str = &curproxy->http.fwd.p_by.nn_expr_s;
-                               expr = &curproxy->http.fwd.p_by.nn_expr;
-                               smp = (curproxy->http.fwd.p_by.nn_mode ==
-                                      HTTP_7239_FORBY_SMP);
+                               expr_str = &fwd->p_by.nn_expr_s;
+                               expr = &fwd->p_by.nn_expr;
+                               smp = (fwd->p_by.nn_mode == HTTP_7239_FORBY_SMP);
                                break;
                        case 2:
                                /* by->nodeport */
-                               expr_str = &curproxy->http.fwd.p_by.np_expr_s;
-                               expr = &curproxy->http.fwd.p_by.np_expr;
-                               smp = (curproxy->http.fwd.p_by.np_mode ==
-                                      HTTP_7239_FORBY_SMP);
+                               expr_str = &fwd->p_by.np_expr_s;
+                               expr = &fwd->p_by.np_expr;
+                               smp = (fwd->p_by.np_mode == HTTP_7239_FORBY_SMP);
                                break;
                        case 3:
                                /* for->node */
-                               expr_str = &curproxy->http.fwd.p_for.nn_expr_s;
-                               expr = &curproxy->http.fwd.p_for.nn_expr;
-                               smp = (curproxy->http.fwd.p_for.nn_mode ==
-                                      HTTP_7239_FORBY_SMP);
+                               expr_str = &fwd->p_for.nn_expr_s;
+                               expr = &fwd->p_for.nn_expr;
+                               smp = (fwd->p_for.nn_mode == HTTP_7239_FORBY_SMP);
                                break;
                        case 4:
                                /* for->nodeport */
-                               expr_str = &curproxy->http.fwd.p_for.np_expr_s;
-                               expr = &curproxy->http.fwd.p_for.np_expr;
-                               smp = (curproxy->http.fwd.p_for.np_mode ==
-                                      HTTP_7239_FORBY_SMP);
+                               expr_str = &fwd->p_for.np_expr_s;
+                               expr = &fwd->p_for.np_expr;
+                               smp = (fwd->p_for.np_mode == HTTP_7239_FORBY_SMP);
                                break;
                }
                if (!smp)
@@ -1123,7 +1142,7 @@ int proxy_http_compile_7239(struct proxy *curproxy)
                        /* should not happen unless system memory exhaustion */
                        ha_alert("%s '%s' [%s:%d]: failed to parse 'option forwarded' expression : %s.\n",
                                 proxy_type_str(curproxy), curproxy->id,
-                                curproxy->http.fwd.c_file, curproxy->http.fwd.c_line,
+                                fwd->c_file, fwd->c_line,
                                 "memory error");
                        cfgerr++;
                        continue;
@@ -1131,14 +1150,14 @@ int proxy_http_compile_7239(struct proxy *curproxy)
 
                cur_expr =
                        sample_parse_expr((char*[]){*expr_str, NULL}, &idx,
-                                         curproxy->http.fwd.c_file,
-                                         curproxy->http.fwd.c_line,
+                                         fwd->c_file,
+                                         fwd->c_line,
                                          &err, &curproxy->conf.args, NULL);
 
                if (!cur_expr) {
                        ha_alert("%s '%s' [%s:%d]: failed to parse 'option forwarded' expression '%s' in : %s.\n",
                                 proxy_type_str(curproxy), curproxy->id,
-                                curproxy->http.fwd.c_file, curproxy->http.fwd.c_line,
+                                fwd->c_file, fwd->c_line,
                                 *expr_str, err);
                        ha_free(&err);
                        cfgerr++;
@@ -1153,7 +1172,7 @@ int proxy_http_compile_7239(struct proxy *curproxy)
                                   "some args extract information from '%s', "
                                   "none of which is available here.\n",
                                   proxy_type_str(curproxy), curproxy->id,
-                                  curproxy->http.fwd.c_file, curproxy->http.fwd.c_line,
+                                  fwd->c_file, fwd->c_line,
                                   *expr_str, sample_ckp_names(cur_expr->fetch->use));
                }
                /* post parsing individual expr cleanup */
@@ -1166,10 +1185,10 @@ int proxy_http_compile_7239(struct proxy *curproxy)
        curproxy->conf.args.line = 0;
 
        /* post parsing general cleanup */
-       ha_free(&curproxy->http.fwd.c_file);
-       curproxy->http.fwd.c_line = 0;
+       ha_free(&fwd->c_file);
+       fwd->c_line = 0;
 
-       curproxy->http.fwd.c_mode = 1; /* parsing completed */
+       fwd->c_mode = 1; /* parsing completed */
 
  out:
        return cfgerr;
@@ -1180,21 +1199,25 @@ int proxy_http_parse_xff(char **args, int cur_arg,
                          struct proxy *curproxy, const struct proxy *defpx,
                          const char *file, int linenum)
 {
+       struct http_ext_xff *xff;
        int err_code = 0;
 
+       if (!http_ext_xff_prepare(curproxy))
+               return proxy_http_parse_oom(file, linenum);
+
+       xff = curproxy->http_ext->xff;
+
        /* 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;
+       xff->mode = HTTP_XFF_ALWAYS;
 
-       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))
+       istfree(&xff->hdr_name);
+       xff->hdr_name = istdup(ist(DEF_XFORWARDFOR_HDR));
+       if (!isttest(xff->hdr_name))
                return proxy_http_parse_oom(file, linenum);
-       curproxy->http.xff.except_net.family = AF_UNSPEC;
+       xff->except_net.family = AF_UNSPEC;
 
        /* loop to go through arguments - start at 2, since 0+1 = "option" "forwardfor" */
        cur_arg = 2;
@@ -1205,16 +1228,16 @@ int proxy_http_parse_xff(char **args, int cur_arg,
 
                        /* 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;
+                           str2net(args[cur_arg+1], 1, &xff->except_net.addr.v4.ip, &xff->except_net.addr.v4.mask)) {
+                               xff->except_net.family = AF_INET;
+                               xff->except_net.addr.v4.ip.s_addr &= 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);
+                                str62net(args[cur_arg+1], &xff->except_net.addr.v6.ip, &mask)) {
+                               xff->except_net.family = AF_INET6;
+                               len2mask6(mask, &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];
+                                       xff->except_net.addr.v6.ip.s6_addr[i] &= xff->except_net.addr.v6.mask.s6_addr[i];
                        }
                        else {
                                ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
@@ -1232,13 +1255,13 @@ int proxy_http_parse_xff(char **args, int 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))
+                       istfree(&xff->hdr_name);
+                       xff->hdr_name = istdup(ist(args[cur_arg+1]));
+                       if (!isttest(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;
+                       xff->mode = HTTP_XFF_IFNONE;
                        cur_arg += 1;
                } else {
                        /* unknown suboption - catchall */
@@ -1257,19 +1280,23 @@ int proxy_http_parse_xot(char **args, int cur_arg,
                          struct proxy *curproxy, const struct proxy *defpx,
                          const char *file, int linenum)
 {
+       struct http_ext_xot *xot;
        int err_code = 0;
 
+       if (!http_ext_xot_prepare(curproxy))
+               return proxy_http_parse_oom(file, linenum);
+
+       xot = curproxy->http_ext->xot;
+
        /* insert x-original-to field, but not for the IP address listed as an except.
         * set default options (ie: bitfield, header name, etc)
         */
 
-       curproxy->options |= PR_O_HTTP_XOT;
-
-       istfree(&curproxy->http.xot.hdr_name);
-       curproxy->http.xot.hdr_name = istdup(ist(DEF_XORIGINALTO_HDR));
-       if (!isttest(curproxy->http.xot.hdr_name))
+       istfree(&xot->hdr_name);
+       xot->hdr_name = istdup(ist(DEF_XORIGINALTO_HDR));
+       if (!isttest(xot->hdr_name))
                return proxy_http_parse_oom(file, linenum);
-       curproxy->http.xot.except_net.family = AF_UNSPEC;
+       xot->except_net.family = AF_UNSPEC;
 
        /* loop to go through arguments - start at 2, since 0+1 = "option" "originalto" */
        cur_arg = 2;
@@ -1280,16 +1307,16 @@ int proxy_http_parse_xot(char **args, int cur_arg,
 
                        /* suboption except - needs additional argument for it */
                        if (*(args[cur_arg+1]) &&
-                           str2net(args[cur_arg+1], 1, &curproxy->http.xot.except_net.addr.v4.ip, &curproxy->http.xot.except_net.addr.v4.mask)) {
-                               curproxy->http.xot.except_net.family = AF_INET;
-                               curproxy->http.xot.except_net.addr.v4.ip.s_addr &= curproxy->http.xot.except_net.addr.v4.mask.s_addr;
+                           str2net(args[cur_arg+1], 1, &xot->except_net.addr.v4.ip, &xot->except_net.addr.v4.mask)) {
+                               xot->except_net.family = AF_INET;
+                               xot->except_net.addr.v4.ip.s_addr &= xot->except_net.addr.v4.mask.s_addr;
                        }
                        else if (*(args[cur_arg+1]) &&
-                                str62net(args[cur_arg+1], &curproxy->http.xot.except_net.addr.v6.ip, &mask)) {
-                               curproxy->http.xot.except_net.family = AF_INET6;
-                               len2mask6(mask, &curproxy->http.xot.except_net.addr.v6.mask);
+                                str62net(args[cur_arg+1], &xot->except_net.addr.v6.ip, &mask)) {
+                               xot->except_net.family = AF_INET6;
+                               len2mask6(mask, &xot->except_net.addr.v6.mask);
                                for (i = 0; i < 16; i++)
-                                       curproxy->http.xot.except_net.addr.v6.ip.s6_addr[i] &= curproxy->http.xot.except_net.addr.v6.mask.s6_addr[i];
+                                       xot->except_net.addr.v6.ip.s6_addr[i] &= xot->except_net.addr.v6.mask.s6_addr[i];
                        }
                        else {
                                ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
@@ -1306,9 +1333,9 @@ int proxy_http_parse_xot(char **args, int cur_arg,
                                err_code |= ERR_ALERT | ERR_FATAL;
                                goto out;
                        }
-                       istfree(&curproxy->http.xot.hdr_name);
-                       curproxy->http.xot.hdr_name = istdup(ist(args[cur_arg+1]));
-                       if (!isttest(curproxy->http.xot.hdr_name))
+                       istfree(&xot->hdr_name);
+                       xot->hdr_name = istdup(ist(args[cur_arg+1]));
+                       if (!isttest(xot->hdr_name))
                                return proxy_http_parse_oom(file, linenum);
                        cur_arg += 2;
                } else {
@@ -1329,8 +1356,105 @@ int proxy_http_parse_xot(char **args, int cur_arg,
  * below are helpers to manage http ext options
  */
 
-void http_ext_7239_clean(struct http_ext_7239 *clean)
+/* Ensure http_ext->fwd is properly allocated and
+ * initialized for <curproxy>.
+ * The function will leverage http_ext_prepare() to make
+ * sure http_ext is properly allocated and initialized as well.
+ * Returns 1 for success and 0 for failure (memory error)
+ */
+int http_ext_7239_prepare(struct proxy *curproxy)
+{
+       struct http_ext_7239 *fwd;
+
+       if (!http_ext_prepare(curproxy))
+               return 0;
+       if (curproxy->http_ext->fwd)
+               return 1; /* nothing to do */
+
+       fwd = malloc(sizeof(*fwd));
+       if (!fwd)
+               return 0;
+       /* initialize fwd mandatory fields */
+       fwd->c_mode = 0; /* pre-compile (parse) time */
+       fwd->c_file = NULL;
+       fwd->p_host.expr_s = NULL;
+       fwd->p_by.nn_expr_s = NULL;
+       fwd->p_by.np_expr_s = NULL;
+       fwd->p_for.nn_expr_s = NULL;
+       fwd->p_for.np_expr_s = NULL;
+       /* assign */
+       curproxy->http_ext->fwd = fwd;
+       return 1;
+}
+
+/* Ensure http_ext->xff is properly allocated and
+ * initialized for <curproxy>.
+ * The function will leverage http_ext_prepare() to make
+ * sure http_ext is properly allocated and initialized as well.
+ * Returns 1 for success and 0 for failure (memory error)
+ */
+int http_ext_xff_prepare(struct proxy *curproxy)
+{
+       struct http_ext_xff *xff;
+
+       if (!http_ext_prepare(curproxy))
+               return 0;
+       if (curproxy->http_ext->xff)
+               return 1; /* nothing to do */
+
+       xff = malloc(sizeof(*xff));
+       if (!xff)
+               return 0;
+       /* initialize xff mandatory fields */
+       xff->hdr_name = IST_NULL;
+       /* assign */
+       curproxy->http_ext->xff = xff;
+       return 1;
+}
+
+/* Ensure http_ext->xot is properly allocated and
+ * initialized for <curproxy>.
+ * The function will leverage http_ext_prepare() to make
+ * sure http_ext is properly allocated and initialized as well.
+ * Returns 1 for success and 0 for failure (memory error)
+ */
+int http_ext_xot_prepare(struct proxy *curproxy)
+{
+       struct http_ext_xot *xot;
+
+       if (!http_ext_prepare(curproxy))
+               return 0;
+       if (curproxy->http_ext->xot)
+               return 1; /* nothing to do */
+
+       xot = malloc(sizeof(*xot));
+       if (!xot)
+               return 0;
+       /* initialize xot mandatory fields */
+       xot->hdr_name = IST_NULL;
+       /* assign */
+       curproxy->http_ext->xot = xot;
+       return 1;
+}
+
+/* deep clean http_ext->fwd parameter for <curproxy>
+ * http_ext->fwd will be freed
+ * clean behavior will differ depending on http_ext->fwd
+ * state. If fwd is in 'parsed' state, parsing hints will be
+ * cleaned. Else, it means fwd is in 'compiled' state, in this
+ * case we're cleaning compiled results.
+ * This is because parse and compile memory areas are shared in
+ * a single union to optimize struct http_ext_7239 size.
+ */
+void http_ext_7239_clean(struct proxy *curproxy)
 {
+       struct http_ext_7239 *clean;
+
+       if (!curproxy->http_ext)
+               return;
+       clean = curproxy->http_ext->fwd;
+       if (!clean)
+               return; /* nothing to do */
        if (!clean->c_mode) {
                /* parsed */
                ha_free(&clean->c_file);
@@ -1353,22 +1477,69 @@ void http_ext_7239_clean(struct http_ext_7239 *clean)
                release_sample_expr(clean->p_for.np_expr);
                clean->p_for.np_expr = NULL;
        }
+       /* free fwd */
+       ha_free(&curproxy->http_ext->fwd);
 }
 
-void http_ext_xff_clean(struct http_ext_xff *clean)
+/* deep clean http_ext->xff parameter for <curproxy>
+ * http_ext->xff will be freed
+ */
+void http_ext_xff_clean(struct proxy *curproxy)
 {
+       struct http_ext_xff *clean;
+
+       if (!curproxy->http_ext)
+               return;
+       clean = curproxy->http_ext->xff;
+       if (!clean)
+               return; /* nothing to do */
        istfree(&clean->hdr_name);
+       /* free xff */
+       ha_free(&curproxy->http_ext->xff);
 }
 
-void http_ext_xot_clean(struct http_ext_xot *clean)
+/* deep clean http_ext->xot parameter for <curproxy>
+ * http_ext->xot will be freed
+ */
+void http_ext_xot_clean(struct proxy *curproxy)
 {
+       struct http_ext_xot *clean;
+
+       if (!curproxy->http_ext)
+               return;
+       clean = curproxy->http_ext->xot;
+       if (!clean)
+               return; /* nothing to do */
        istfree(&clean->hdr_name);
+       /* free xot */
+       ha_free(&curproxy->http_ext->xot);
 }
 
-void http_ext_7239_copy(struct http_ext_7239 *dest, const struct http_ext_7239 *orig)
+/* duplicate http_ext->fwd parameters from <def> to <cpy>
+ * performs the required memory allocation and initialization
+ */
+void http_ext_7239_dup(const struct proxy *def, struct proxy *cpy)
 {
+       struct http_ext_7239 *dest = NULL;
+       struct http_ext_7239 *orig = NULL;
+
+       /* feature requires backend cap */
+       if (!(cpy->cap & PR_CAP_BE))
+               return;
+
+       if (def->http_ext == NULL || def->http_ext->fwd == NULL)
+               return;
+
+       orig = def->http_ext->fwd;
+
        if (orig->c_mode)
                return; /* copy not supported once compiled */
+
+       if (!http_ext_7239_prepare(cpy))
+               return;
+
+       dest = cpy->http_ext->fwd;
+
        if (orig->c_file)
                dest->c_file = strdup(orig->c_file);
        dest->c_line = orig->c_line;
@@ -1396,21 +1567,104 @@ void http_ext_7239_copy(struct http_ext_7239 *dest, const struct http_ext_7239 *
                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)
+/* duplicate http_ext->xff parameters from <def> to <cpy>
+ * performs the required memory allocation and initialization
+ */
+void http_ext_xff_dup(const struct proxy *def, struct proxy *cpy)
 {
+       struct http_ext_xff *dest = NULL;
+       struct http_ext_xff *orig = NULL;
+
+       if (def->http_ext == NULL || def->http_ext->xff == NULL ||
+           !http_ext_xff_prepare(cpy))
+               return;
+
+       orig = def->http_ext->xff;
+       dest = cpy->http_ext->xff;
+
        if (isttest(orig->hdr_name))
                dest->hdr_name = istdup(orig->hdr_name);
        dest->mode = orig->mode;
        dest->except_net = orig->except_net;
 }
 
-void http_ext_xot_copy(struct http_ext_xot *dest, const struct http_ext_xot *orig)
+/* duplicate http_ext->xot parameters from <def> to <cpy>
+ * performs the required memory allocation and initialization
+ */
+void http_ext_xot_dup(const struct proxy *def, struct proxy *cpy)
 {
+       struct http_ext_xot *dest = NULL;
+       struct http_ext_xot *orig = NULL;
+
+       if (def->http_ext == NULL || def->http_ext->xot == NULL ||
+           !http_ext_xot_prepare(cpy))
+               return;
+
+       orig = def->http_ext->xot;
+       dest = cpy->http_ext->xot;
+
        if (isttest(orig->hdr_name))
                dest->hdr_name = istdup(orig->hdr_name);
        dest->except_net = orig->except_net;
 }
 
+/* Allocate new http_ext and initialize it
+ * if needed
+ * Returns 1 for success and 0 for failure
+ */
+int http_ext_prepare(struct proxy *curproxy)
+{
+       if (curproxy->http_ext)
+               return 1; /* nothing to do */
+
+       curproxy->http_ext = malloc(sizeof(*curproxy->http_ext));
+       if (!curproxy->http_ext)
+               return 0; /* failure */
+       /* first init, set supported ext to NULL */
+       curproxy->http_ext->fwd = NULL;
+       curproxy->http_ext->xff = NULL;
+       curproxy->http_ext->xot = NULL;
+       return 1;
+}
+
+/* duplicate existing http_ext from <defproxy> to <curproxy>
+ */
+void http_ext_dup(const struct proxy *defproxy, struct proxy *curproxy)
+{
+       /* copy defproxy.http_ext members */
+       http_ext_7239_dup(defproxy, curproxy);
+       http_ext_xff_dup(defproxy, curproxy);
+       http_ext_xot_dup(defproxy, curproxy);
+}
+
+/* deep clean http_ext for <curproxy> (if previously allocated)
+ */
+void http_ext_clean(struct proxy *curproxy)
+{
+       if (!curproxy->http_ext)
+               return; /* nothing to do */
+       /* first, free supported ext */
+       http_ext_7239_clean(curproxy);
+       http_ext_xff_clean(curproxy);
+       http_ext_xot_clean(curproxy);
+
+       /* then, free http_ext */
+       ha_free(&curproxy->http_ext);
+}
+
+/* soft clean (only clean http_ext if no more options are used) */
+void http_ext_softclean(struct proxy *curproxy)
+{
+       if (!curproxy->http_ext)
+               return; /* nothing to do */
+       if (!curproxy->http_ext->fwd &&
+           !curproxy->http_ext->xff &&
+           !curproxy->http_ext->xot) {
+               /* no more use for http_ext, all options are disabled */
+               http_ext_clean(curproxy);
+       }
+}
+
 /*
  * =========== CONV ===========
  * related converters
index efb8ddbe1bf2d187c7de452aedad8ac416ecde33..43a35f54633f79b6e4b8f25b6335f635a736063a 100644 (file)
@@ -352,9 +352,8 @@ void free_proxy(struct proxy *p)
                pxdf->fct(p);
 
        free(p->desc);
-       http_ext_7239_clean(&p->http.fwd);
-       http_ext_xff_clean(&p->http.xff);
-       http_ext_xot_clean(&p->http.xot);
+
+       http_ext_clean(p);
 
        task_destroy(p->task);
 
@@ -1467,9 +1466,7 @@ void proxy_free_defaults(struct proxy *defproxy)
        ha_free(&defproxy->conn_src.iface_name);
        istfree(&defproxy->server_id_hdr_name);
 
-       http_ext_7239_clean(&defproxy->http.fwd);
-       http_ext_xff_clean(&defproxy->http.xff);
-       http_ext_xot_clean(&defproxy->http.xot);
+       http_ext_clean(defproxy);
 
        list_for_each_entry_safe(acl, aclb, &defproxy->acl, list) {
                LIST_DELETE(&acl->list);
@@ -1650,10 +1647,8 @@ static int proxy_defproxy_cpy(struct proxy *curproxy, const struct proxy *defpro
        curproxy->tcp_req.inspect_delay = defproxy->tcp_req.inspect_delay;
        curproxy->tcp_rep.inspect_delay = defproxy->tcp_rep.inspect_delay;
 
-       if (defproxy->options & PR_O_HTTP_XFF)
-               http_ext_xff_copy(&curproxy->http.xff, &defproxy->http.xff);
-       if (defproxy->options & PR_O_HTTP_XOT)
-               http_ext_xot_copy(&curproxy->http.xot, &defproxy->http.xot);
+       http_ext_clean(curproxy);
+       http_ext_dup(defproxy, curproxy);
 
        if (isttest(defproxy->server_id_hdr_name))
                curproxy->server_id_hdr_name = istdup(defproxy->server_id_hdr_name);
@@ -1697,8 +1692,6 @@ static int proxy_defproxy_cpy(struct proxy *curproxy, const struct proxy *defpro
                }
 
                curproxy->ck_opts = defproxy->ck_opts;
-               if (defproxy->options & PR_O_HTTP_7239)
-                       http_ext_7239_copy(&curproxy->http.fwd, &defproxy->http.fwd);
 
                if (defproxy->cookie_name)
                        curproxy->cookie_name = strdup(defproxy->cookie_name);