From: Aurelien DARRAGON Date: Wed, 28 Dec 2022 17:53:05 +0000 (+0100) Subject: MINOR: proxy: move 'forwardfor' option to http_ext X-Git-Tag: v2.8-dev3~72 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=730b9836a6ff95894fd49ed3ba32c8b80693281f;p=thirdparty%2Fhaproxy.git MINOR: proxy: move 'forwardfor' option to http_ext 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. --- diff --git a/include/haproxy/http_ext-t.h b/include/haproxy/http_ext-t.h index b5b22206fb..65a105c218 100644 --- a/include/haproxy/http_ext-t.h +++ b/include/haproxy/http_ext-t.h @@ -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 */ diff --git a/include/haproxy/http_ext.h b/include/haproxy/http_ext.h index 481298cc1b..7f12830ba1 100644 --- a/include/haproxy/http_ext.h +++ b/include/haproxy/http_ext.h @@ -30,12 +30,16 @@ 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 */ diff --git a/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h index 395df2d6a0..0f682ef45f 100644 --- a/include/haproxy/proxy-t.h +++ b/include/haproxy/proxy-t.h @@ -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 */ diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index 66a0dc5ef2..112fac3254 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -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
[/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 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; diff --git a/src/cfgparse.c b/src/cfgparse.c index 15e22968bd..358a587528 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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) { diff --git a/src/http_ana.c b/src/http_ana.c index bbc6f4dc1b..36014718ab 100644 --- a/src/http_ana.c +++ b/src/http_ana.c @@ -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; } /* diff --git a/src/http_ext.c b/src/http_ext.c index fcb5a07bc3..e5c39c2b0a 100644 --- a/src/http_ext.c +++ b/src/http_ext.c @@ -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
[/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 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; +} diff --git a/src/proxy.c b/src/proxy.c index 9166a324ce..802d3c790d 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -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);