diff --git a/Makefile b/Makefile index 933a54da87..4dc52c1c3b 100644 --- a/Makefile +++ b/Makefile @@ -913,7 +913,7 @@ OBJS += src/mux_h2.o src/mux_fcgi.o src/mux_h1.o src/tcpcheck.o \ src/tcp_rules.o src/sink.o src/h1_htx.o src/task.o src/mjson.o \ src/h2.o src/filters.o src/server_state.o src/payload.o \ src/fcgi-app.o src/map.o src/htx.o src/h1.o src/pool.o \ - src/cfgparse-global.o src/trace.o src/tcp_sample.o \ + src/cfgparse-global.o src/trace.o src/tcp_sample.o src/http_ext.o \ src/flt_http_comp.o src/mux_pt.o src/flt_trace.o src/mqtt.o \ src/acl.o src/sock.o src/mworker.o src/tcp_act.o src/ring.o \ src/session.o src/proto_tcp.o src/fd.o src/channel.o src/activity.o \ diff --git a/doc/configuration.txt b/doc/configuration.txt index aa42956868..2d70047f0c 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -4202,6 +4202,7 @@ option dontlog-normal (*) X X X - option dontlognull (*) X X X - -- keyword -------------------------- defaults - frontend - listen -- backend - option forwardfor X X X X +option forwarded (*) X - X X option h1-case-adjust-bogus-client (*) X X X - option h1-case-adjust-bogus-server (*) X - X X option http-buffer-request (*) X X X X @@ -9059,6 +9060,140 @@ no option dontlognull See also : "log", "http-ignore-probes", "monitor-uri", and section 8 about logging. +option forwarded [ proto ] + [ host | host-expr ] + [ by | by-expr ] [ by_port | by_port-expr ] + [ for | for-expr ] [ for_port | for_port-expr ] +no option forwarded + Enable insertion of the rfc 7239 forwarded header in requests sent to servers + May be used in sections : defaults | frontend | listen | backend + yes | no | yes | yes + Arguments : + optional argument to specify a custom sample expression + those result will be used as 'host' parameter value + + optional argument to specicy a custom sample expression + those result will be used as 'by' parameter nodename value + + optional argument to specicy a custom sample expression + those result will be used as 'for' parameter nodename value + + optional argument to specicy a custom sample expression + those result will be used as 'by' parameter nodeport value + + optional argument to specicy a custom sample expression + those result will be used as 'for' parameter nodeport value + + + Since HAProxy works in reverse-proxy mode, servers are loosing some request + context (request origin: client ip address, protocol used...) + + A common way to address this limitation is to use the well known + x-forward-for and x-forward-* friends to expose some of this context to the + underlying servers/applications. + While this use to work and is widely deployed, it is not officially supported + by the IETF and can be the root of some interoperability as well as security + issues. + + To solve this, a new HTTP extension has been described by the IETF: + forwarded header (RFC7239). + More information here: https://www.rfc-editor.org/rfc/rfc7239.html + + The use of this single header allow to convey multiple informations + within the same header, and most importantly, fixes the proxy chaining + issue. (the rfc allows for multiple chained proxies to append their own + values to an already existing header). + + This option may be specified in defaults, listen or backend section, but it + will be ignored for frontend sections. + + Setting option forwarded without arguments results in using default implicit + behavior. + Default behavior enables proto parameter and injects original client ip. + + The equivalent explicit/manual configuration would be: + option forwarded proto for + + The keyword 'by' is used to enable 'by' parameter ("nodename") in + forwarded header. It allows to embed request proxy information. + 'by' value will be set to proxy ip (destination address) + If not available (ie: UNIX listener), 'by' will be set to + "unknown". + + The keyword 'by-expr' is used to enable 'by' parameter ("nodename") in + forwarded header. It allows to embed request proxy information. + 'by' value will be set to the result of the sample expression + , if valid, otherwise it will be set to "unknown". + + The keyword 'for' is used to enable 'for' parameter ("nodename") in + forwarded header. It allows to embed request client information. + 'for' value will be set to client ip (source address) + If not available (ie: UNIX listener), 'for' will be set to + "unknown". + + The keyword 'for-expr' is used to enable 'for' parameter ("nodename") in + forwarded header. It allows to embed request client information. + 'for' value will be set to the result of the sample expression + , if valid, otherwise it will be set to "unknown". + + The keyword 'by_port' is used to provide "nodeport" info to + 'by' parameter. 'by_port' requires 'by' or 'by-expr' to be set or + it will be ignored. + "nodeport" will be set to proxy (destination) port if available, + otherwise it will be ignored. + + The keyword 'by_port-expr' is used to provide "nodeport" info to + 'by' parameter. 'by_port-expr' requires 'by' or 'by-expr' to be set or + it will be ignored. + "nodeport" will be set to the result of the sample expression + , if valid, otherwise it will be ignored. + + The keyword 'for_port' is used to provide "nodeport" info to + 'for' parameter. 'for_port' requires 'for' or 'for-expr' to be set or + it will be ignored. + "nodeport" will be set to client (source) port if available, + otherwise it will be ignored. + + The keyword 'for_port-expr' is used to provide "nodeport" info to + 'for' parameter. 'for_port-expr' requires 'for' or 'for-expr' to be set or + it will be ignored. + "nodeport" will be set to the result of the sample expression + , if valid, otherwise it will be ignored. + + Examples : + # Those servers want the ip address and protocol of the client request + # Resulting header would look like this: + # forwarded: proto=http;for=127.0.0.1 + backend www_default + mode http + option forwarded + #equivalent to: option forwarded proto for + + # Those servers want the requested host and hashed client ip address + # as well as client source port (you should use seed for xxh32 if ensuring + # ip privacy is a concern) + # Resulting header would look like this: + # forwarded: host="haproxy.org";for="_000000007F2F367E:60138" + backend www_host + mode http + option forwarded host for-expr src,xxh32,hex for_port + + # Those servers want custom data in host, for and by parameters + # Resulting header would look like this: + # forwarded: host="host.com";by=_haproxy;for="[::1]:10" + backend www_custom + mode http + option forwarded host-expr str(host.com) by-expr str(_haproxy) for for_port-expr int(10) + + # Those servers want random 'for' obfuscated identifiers for request + # tracing purposes while protecting sensitive IP information + # Resulting header would look like this: + # forwarded: for=_000000002B1F4D63 + backend www_for_hide + mode http + option forwarded for-expr rand,hex + + See also : "option forwardfor", "option originalto" option forwardfor [ except ] [ header ] [ if-none ] Enable insertion of the X-Forwarded-For header to requests sent to servers diff --git a/include/haproxy/http_ext-t.h b/include/haproxy/http_ext-t.h new file mode 100644 index 0000000000..b5b22206fb --- /dev/null +++ b/include/haproxy/http_ext-t.h @@ -0,0 +1,113 @@ +/* + * include/haproxy/http_ext-t.h + * Version-agnostic and implementation-agnostic HTTP extensions definitions + * + * Copyright 2022 HAProxy Technologies + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _HAPROXY_HTTPEXT_T_H +#define _HAPROXY_HTTPEXT_T_H + +#include +#include +#include + +enum forwarded_header_attribute_type { + FORWARDED_HEADER_UNK = 0, + FORWARDED_HEADER_OBFS = 1, + FORWARDED_HEADER_PORT = 2, + FORWARDED_HEADER_IP = 3, +}; + +struct forwarded_header_nodename { + union { + struct sockaddr_storage ip; + struct ist obfs; + }; + enum forwarded_header_attribute_type type; +}; + +struct forwarded_header_nodeport { + union { + uint16_t port; + struct ist obfs; + }; + enum forwarded_header_attribute_type type; +}; + +struct forwarded_header_node { + struct forwarded_header_nodename nodename; + struct forwarded_header_nodeport nodeport; + struct ist raw; +}; + +enum forwarded_header_proto { + FORWARDED_HEADER_HTTP = 1, + FORWARDED_HEADER_HTTPS = 2 +}; + +struct forwarded_header_ctx { + struct forwarded_header_node nfor; + struct forwarded_header_node nby; + struct ist host; + enum forwarded_header_proto proto; +}; + +enum http_ext_7239_forby_mode { + HTTP_7239_FORBY_ORIG = 1, + HTTP_7239_FORBY_SMP = 2 +}; +struct http_ext_7239_forby { + /* nn = nodename, np = nodeport */ + char *nn_expr_s; + struct sample_expr *nn_expr; + char *np_expr_s; + struct sample_expr *np_expr; + enum http_ext_7239_forby_mode nn_mode; + enum http_ext_7239_forby_mode np_mode; +}; + +enum http_ext_7239_host_mode { + HTTP_7239_HOST_ORIG = 1, + HTTP_7239_HOST_SMP = 2 +}; +struct http_ext_7239_host { + char *expr_s; + struct sample_expr *expr; + enum http_ext_7239_host_mode mode; +}; + +struct http_ext_7239 { + /* forwarded header parameters options */ + struct http_ext_7239_forby p_for; + struct http_ext_7239_forby p_by; + struct http_ext_7239_host p_host; + uint8_t p_proto; + /* config error hints, used only during configuration parsing */ + char *c_file; + int c_line; +}; + +enum forwarded_header_field { + FORWARDED_HEADER_FOR = 0x01, + FORWARDED_HEADER_BY = 0x02, + FORWARDED_HEADER_HOST = 0x04, + FORWARDED_HEADER_PROTO = 0x08, + FORWARDED_HEADER_ALL = FORWARDED_HEADER_FOR|FORWARDED_HEADER_BY|FORWARDED_HEADER_HOST|FORWARDED_HEADER_PROTO +}; + +#endif /* !_HAPROXY_HTTPEXT_T_H */ diff --git a/include/haproxy/http_ext.h b/include/haproxy/http_ext.h new file mode 100644 index 0000000000..481298cc1b --- /dev/null +++ b/include/haproxy/http_ext.h @@ -0,0 +1,41 @@ +/* + * include/haproxy/http_ext.h + * Functions for Version-agnostic and implementation-agnostic HTTP extensions + * + * Copyright 2022 HAProxy Technologies + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _HAPROXY_HTTPEXT_H +#define _HAPROXY_HTTPEXT_H + +#include +#include +#include +#include + +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); + +void http_ext_7239_clean(struct http_ext_7239 *); + +void http_ext_7239_copy(struct http_ext_7239 *dest, const struct http_ext_7239 *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); + +#endif /* !_HAPROXY_HTTPEXT_H */ diff --git a/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h index e863299cfb..395df2d6a0 100644 --- a/include/haproxy/proxy-t.h +++ b/include/haproxy/proxy-t.h @@ -43,6 +43,7 @@ #include #include #include +#include /* values for proxy->mode */ enum pr_mode { @@ -100,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 */ -/* unused: 0x00020000 */ +#define PR_O_HTTP_7239 0x00200000 /* insert 7239 forwarded header */ #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 */ @@ -268,6 +269,8 @@ struct error_snapshot { /* http options */ struct proxy_http { + /* forwarded header (RFC 7239) */ + struct http_ext_7239 fwd; }; struct proxy { diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index 258564404a..66a0dc5ef2 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -61,7 +62,7 @@ static const char *common_options[] = { "redispatch", "httplog", "tcplog", "tcpka", "httpchk", "ssl-hello-chk", "smtpchk", "pgsql-check", "redis-check", "mysql-check", "ldap-check", "spop-check", "tcp-check", - "external-check", "forwardfor", "original-to", + "external-check", "forwardfor", "original-to", "forwarded", NULL /* must be last */ }; @@ -2031,6 +2032,16 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) err_code |= ERR_ALERT | ERR_FATAL; goto out; } + else if (strcmp(args[1], "forwarded") == 0) { + if (kwm == KWM_STD) { + err_code |= proxy_http_parse_7239(args, 0, curproxy, curr_defproxy, file, linenum); + goto out; + } + else if (kwm == KWM_NO) { + curproxy->options &= ~PR_O_HTTP_7239; + goto out; + } + } /* Redispatch can take an integer argument that control when the * resispatch occurs. All values are relative to the retries option. diff --git a/src/cfgparse.c b/src/cfgparse.c index 6f2d93a055..15e22968bd 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -3669,6 +3670,9 @@ int check_config_validity() 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) + cfgerr += proxy_http_compile_7239(curproxy); /* only now we can check if some args remain unresolved. * This must be done after the users and groups resolution. @@ -3964,6 +3968,13 @@ int check_config_validity() 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_FWDFOR | PR_O_FF_ALWAYS)) { ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n", "forwardfor", proxy_type_str(curproxy), curproxy->id); diff --git a/src/http_ana.c b/src/http_ana.c index 1d9423af48..bbc6f4dc1b 100644 --- a/src/http_ana.c +++ b/src/http_ana.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -661,6 +662,12 @@ 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; + } + /* * 9: add X-Forwarded-For if either the frontend or the backend * asks for it. diff --git a/src/http_ext.c b/src/http_ext.c new file mode 100644 index 0000000000..fcb5a07bc3 --- /dev/null +++ b/src/http_ext.c @@ -0,0 +1,1059 @@ +/* + * HTTP extensions logic and helpers + * + * Copyright 2022 HAProxy Technologies + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * + */ + +/* forwarded header (7239 RFC) */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* check if char is a valid obfuscated identifier char + * (according to 7239 RFC) + * Returns non zero value for valid char + */ +static int http_7239_valid_obfsc(char c) +{ + return (isalnum((unsigned char)c) || + (c == '.' || c == '-' || c == '_')); +} + +/* + * =========== ANALYZE =========== + * below are http process/ana helpers + */ + +/* checks if contains rfc7239 compliant port + * Returns 1 for success and 0 for failure + * if is not NULL, it will be set to the extracted value contained + * in + * will be consumed accordingly (parsed/extracted characters are + * removed from ) + */ +static inline int http_7239_extract_port(struct ist *input, uint16_t *port) +{ + char *start = istptr(*input); + uint32_t port_cast = 0; + int it = 0; + + /* strtol does not support non-null terminated str, + * we extract port ourselves + */ + while (it < istlen(*input) && + isdigit((unsigned char)start[it])) { + port_cast = (port_cast * 10) + (start[it] - '0'); + if (port_cast > 65535) + return 0; /* invalid port */ + it += 1; + } + if (!port_cast) + return 0; /* invalid port */ + /* ok */ + if (port) + *port = (uint16_t)port_cast; + *input = istadv(*input, it); + return 1; +} + +/* checks if contains rfc7239 compliant obfuscated identifier + * Returns 1 for success and 0 for failure + * if is not NULL, it will be set to the extracted value contained + * in + * will be consumed accordingly (parsed/extracted characters are + * removed from ) + */ +static inline int http_7239_extract_obfs(struct ist *input, struct ist *obfs) +{ + int it = 0; + + if (obfs) + obfs->ptr = input->ptr; + + while (it < istlen(*input) && istptr(*input)[it] != ';') { + if (!http_7239_valid_obfsc(istptr(*input)[it])) + break; /* end of obfs token */ + it += 1; + } + if (obfs) + obfs->len = it; + *input = istadv(*input, it); + return !!it; +} + +/* checks if contains rfc7239 compliant IPV4 address + * Returns 1 for success and 0 for failure + * if is not NULL, it will be set to the extracted value contained + * in + * will be consumed accordingly (parsed/extracted characters are + * removed from ) + */ +static inline int http_7239_extract_ipv4(struct ist *input, struct in_addr *ip) +{ + char ip4[INET_ADDRSTRLEN]; + unsigned char buf[sizeof(struct in_addr)]; + int it = 0; + + /* extract ipv4 addr */ + while (it < istlen(*input) && it < (sizeof(ip4) - 1)) { + if (!isdigit((unsigned char)istptr(*input)[it]) && + istptr(*input)[it] != '.') + break; /* no more ip4 char */ + ip4[it] = istptr(*input)[it]; + it += 1; + } + ip4[it] = 0; + if (inet_pton(AF_INET, ip4, buf) != 1) + return 0; /* invalid ip4 addr */ + /* ok */ + if (ip) + memcpy(ip, buf, sizeof(buf)); + *input = istadv(*input, it); + return 1; +} + +/* checks if contains rfc7239 compliant IPV6 address + * assuming input.len >= 1 and first char is '[' + * Returns 1 for success and 0 for failure + * if is not NULL, it will be set to the extracted value contained + * in + * will be consumed accordingly (parsed/extracted characters are + * removed from ) + */ +static inline int http_7239_extract_ipv6(struct ist *input, struct in6_addr *ip) +{ + char ip6[INET6_ADDRSTRLEN]; + unsigned char buf[sizeof(struct in6_addr)]; + int it = 0; + + *input = istnext(*input); /* skip '[' leading char */ + /* extract ipv6 addr */ + while (it < istlen(*input) && + it < (sizeof(ip6) - 1)) { + if (!isalnum((unsigned char)istptr(*input)[it]) && + istptr(*input)[it] != ':') + break; /* no more ip6 char */ + ip6[it] = istptr(*input)[it]; + it += 1; + } + ip6[it] = 0; + if ((istlen(*input)-it) < 1 || istptr(*input)[it] != ']') + return 0; /* missing ending "]" char */ + it += 1; + if (inet_pton(AF_INET6, ip6, buf) != 1) + return 0; /* invalid ip6 addr */ + /* ok */ + if (ip) + memcpy(ip, buf, sizeof(buf)); + *input = istadv(*input, it); + return 1; +} + +/* checks if contains rfc7239 compliant host + * is used to determine if the current input is being extracted + * from a quoted (non zero) or unquoted (zero) token, as the parsing rules + * differ wheteher the input is quoted or not according to the rfc. + * Returns 1 for success and 0 for failure + * if is not NULL, it will be set to the extracted value contained + * in + * will be consumed accordingly (parsed/extracted characters are + * removed from ) + */ +static inline int http_7239_extract_host(struct ist *input, struct ist *host, int quoted) +{ + if (istlen(*input) < 1) + return 0; /* invalid input */ + + if (host) + host->ptr = input->ptr; + + if (quoted && *istptr(*input) == '[') { + /* raw ipv6 address */ + if (!http_7239_extract_ipv6(input, NULL)) + return 0; /* invalid addr */ + } + else { + /* ipv4 or dns */ + while (istlen(*input)) { + if (!isalnum((unsigned char)*istptr(*input)) && + *istptr(*input) != '.') + break; /* end of hostname token */ + *input = istnext(*input); + } + } + if (istlen(*input) < 1 || *istptr(*input) != ':') { + goto out; /* no optional port provided */ + } + if (!quoted) + return 0; /* not supported */ + *input = istnext(*input); /* skip ':' */ + /* validate port */ + if (!http_7239_extract_port(input, NULL)) + return 0; /* invalid port */ + out: + if (host) + host->len = (input->ptr - host->ptr); + return 1; +} + +/* checks if contains rfc7239 compliant nodename + * is used to determine if the current input is being extracted + * from a quoted (non zero) or unquoted (zero) token, as the parsing rules + * differ wheteher the input is quoted or not according to the rfc. + * Returns 1 for success and 0 for failure + * if is not NULL, it will be set to the extracted value contained + * in + * will be consumed accordingly (parsed/extracted characters are + * removed from ) + */ +static inline int http_7239_extract_nodename(struct ist *input, struct forwarded_header_nodename *nodename, int quoted) +{ + if (istlen(*input) < 1) + return 0; /* invalid input */ + if (*istptr(*input) == '_') { + struct ist *obfs = NULL; + + /* obfuscated nodename */ + *input = istnext(*input); /* skip '_' */ + if (nodename) { + nodename->type = FORWARDED_HEADER_OBFS; + obfs = &nodename->obfs; + } + if (!http_7239_extract_obfs(input, obfs)) + return 0; /* invalid obfs */ + } else if (*istptr(*input) == 'u') { + /* "unknown" nodename? */ + if (istlen(*input) < 7 || + strncmp("unknown", istptr(*input), 7)) + return 0; /* syntax error */ + *input = istadv(*input, 7); /* skip "unknown" */ + if (nodename) + nodename->type = FORWARDED_HEADER_UNK; + } else if (quoted && *istptr(*input) == '[') { + struct in6_addr *ip6 = NULL; + + /* ipv6 address */ + if (nodename) { + struct sockaddr_in6 *addr = (void *)&nodename->ip; + + ip6 = &addr->sin6_addr; + addr->sin6_family = AF_INET6; + nodename->type = FORWARDED_HEADER_IP; + } + if (!http_7239_extract_ipv6(input, ip6)) + return 0; /* invalid ip6 */ + } else if (*istptr(*input)) { + struct in_addr *ip = NULL; + + /* ipv4 address */ + if (nodename) { + struct sockaddr_in *addr = (void *)&nodename->ip; + + ip = &addr->sin_addr; + addr->sin_family = AF_INET; + nodename->type = FORWARDED_HEADER_IP; + } + if (!http_7239_extract_ipv4(input, ip)) + return 0; /* invalid ip */ + } else + return 0; /* unexpected char */ + + /* ok */ + return 1; +} + +/* checks if contains rfc7239 compliant nodeport + * is used to determine if the current input is being extracted + * from a quoted (non zero) or unquoted (zero) token, as the parsing rules + * differ wheteher the input is quoted or not according to the rfc. + * Returns 1 for success and 0 for failure + * if is not NULL, it will be set to the extracted value contained + * in + * will be consumed accordingly (parsed/extracted characters are + * removed from ) + */ +static inline int http_7239_extract_nodeport(struct ist *input, struct forwarded_header_nodeport *nodeport) +{ + if (*istptr(*input) == '_') { + struct ist *obfs = NULL; + + /* obfuscated nodeport */ + *input = istnext(*input); /* skip '_' */ + if (nodeport) { + nodeport->type = FORWARDED_HEADER_OBFS; + obfs = &nodeport->obfs; + } + if (!http_7239_extract_obfs(input, obfs)) + return 0; /* invalid obfs */ + } else { + uint16_t *port = NULL; + + /* normal port */ + if (nodeport) { + nodeport->type = FORWARDED_HEADER_PORT; + port = &nodeport->port; + } + if (!http_7239_extract_port(input, port)) + return 0; /* invalid port */ + } + /* ok */ + return 1; +} + +/* checks if contains rfc7239 compliant node (nodename:nodeport token) + * is used to determine if the current input is being extracted + * from a quoted (non zero) or unquoted (zero) token, as the parsing rules + * differ wheteher the input is quoted or not according to the rfc. + * Returns 1 for success and 0 for failure + * if is not NULL, it will be set to the extracted value contained + * in + * will be consumed accordingly (parsed/extracted characters are + * removed from ) + */ +static inline int http_7239_extract_node(struct ist *input, struct forwarded_header_node *node, int quoted) +{ + struct forwarded_header_nodename *nodename = NULL; + struct forwarded_header_nodeport *nodeport = NULL; + + if (node) { + nodename = &node->nodename; + nodeport = &node->nodeport; + node->raw.ptr = input->ptr; + } + if (!http_7239_extract_nodename(input, nodename, quoted)) + return 0; /* invalid nodename */ + if (istlen(*input) < 1 || *istptr(*input) != ':') { + if (node) + node->nodeport.type = FORWARDED_HEADER_UNK; + goto out; /* no optional port provided */ + } + if (!quoted) + return 0; /* not supported */ + *input = istnext(*input); + if (!http_7239_extract_nodeport(input, nodeport)) + return 0; /* invalid nodeport */ + out: + /* ok */ + if (node) + node->raw.len = input->ptr - node->raw.ptr; + return 1; +} + +static inline int _forwarded_header_save_ctx(struct forwarded_header_ctx *ctx, int current_step, int required_steps) +{ + return (ctx && (current_step & required_steps)); +} + +static inline void _forwarded_header_quote_expected(struct ist *hdr, uint8_t *quoted) +{ + if (istlen(*hdr) > 0 && *istptr(*hdr) == '"') { + *quoted = 1; + /* node is quoted, we must find corresponding + * ending quote at the end of the token + */ + *hdr = istnext(*hdr); /* skip quote */ + } +} + +/* checks if current header is RFC 7239 compliant and can be "trusted". + * function will stop parsing as soon as every have + * been validated or error is encountered. + * Provide FORWARDED_HEADER_ALL for a full header validating spectrum. + * You may provide limited scope to perform quick searches on specific attributes + * If is provided (not NULL), parsed attributes will be stored according to + * their types, allowing you to extract some useful information from the header. + * Returns 0 on failure and bitfield on success. + */ +int http_validate_7239_header(struct ist hdr, int required_steps, struct forwarded_header_ctx *ctx) +{ + int validated_steps = 0; + int current_step = 0; + uint8_t first = 1; + uint8_t quoted = 0; + + while (istlen(hdr) && (required_steps & ~validated_steps)) { + if (!first) { + if (*istptr(hdr) == ';') + hdr = istnext(hdr); /* skip ';' */ + else + goto not_ok; /* unexpected char */ + } + else + first = 0; + + if (!(validated_steps & FORWARDED_HEADER_FOR) && istlen(hdr) > 4 && + strncmp("for=", istptr(hdr), 4) == 0) { + struct forwarded_header_node *node = NULL; + + /* for parameter */ + current_step = FORWARDED_HEADER_FOR; + hdr = istadv(hdr, 4); /* skip "for=" */ + _forwarded_header_quote_expected(&hdr, "ed); + if (_forwarded_header_save_ctx(ctx, current_step, required_steps)) + node = &ctx->nfor; + /* validate node */ + if (!http_7239_extract_node(&hdr, node, quoted)) + goto not_ok; /* invalid node */ + } + else if (!(validated_steps & FORWARDED_HEADER_BY) && istlen(hdr) > 3 && + strncmp("by=", istptr(hdr), 3) == 0) { + struct forwarded_header_node *node = NULL; + + /* by parameter */ + current_step = FORWARDED_HEADER_BY; + hdr = istadv(hdr, 3); /* skip "by=" */ + _forwarded_header_quote_expected(&hdr, "ed); + if (_forwarded_header_save_ctx(ctx, current_step, required_steps)) + node = &ctx->nby; + /* validate node */ + if (!http_7239_extract_node(&hdr, node, quoted)) + goto not_ok; /* invalid node */ + } + else if (!(validated_steps & FORWARDED_HEADER_HOST) && istlen(hdr) > 5 && + strncmp("host=", istptr(hdr), 5) == 0) { + struct ist *host = NULL; + + /* host parameter */ + current_step = FORWARDED_HEADER_HOST; + hdr = istadv(hdr, 5); /* skip "host=" */ + _forwarded_header_quote_expected(&hdr, "ed); + if (_forwarded_header_save_ctx(ctx, current_step, required_steps)) + host = &ctx->host; + /* validate host */ + if (!http_7239_extract_host(&hdr, host, quoted)) + goto not_ok; /* invalid host */ + } + else if (!(validated_steps & FORWARDED_HEADER_PROTO) && istlen(hdr) > 6 && + strncmp("proto=", istptr(hdr), 6) == 0) { + /* proto parameter */ + current_step = FORWARDED_HEADER_PROTO; + hdr = istadv(hdr, 6); /* skip "proto=" */ + /* validate proto (only common used http|https are supported for now) */ + if (istlen(hdr) < 4 || strncmp("http", istptr(hdr), 4)) + goto not_ok; + hdr = istadv(hdr, 4); /* skip "http" */ + if (istlen(hdr) && *istptr(hdr) == 's') { + hdr = istnext(hdr); + if (_forwarded_header_save_ctx(ctx, current_step, required_steps)) + ctx->proto = FORWARDED_HEADER_HTTPS; + } else if (_forwarded_header_save_ctx(ctx, current_step, required_steps)) + ctx->proto = FORWARDED_HEADER_HTTP; + /* rfc allows for potential proto quoting, but we don't support + * it: it is not common usage + */ + } + else { + /* not supported + * rfc allows for upcoming extensions + * but obviously, we can't trust them + * as they are not yet standardized + */ + + goto not_ok; + } + /* quote check */ + if (quoted) { + if (istlen(hdr) < 1 || *istptr(hdr) != '"') { + /* matching ending quote not found */ + goto not_ok; + } + hdr = istnext(hdr); /* skip ending quote */ + quoted = 0; /* reset */ + } + validated_steps |= current_step; + } + + return validated_steps; + + not_ok: + return 0; +} + +static inline void http_build_7239_header_nodename(struct buffer *out, + struct stream *s, struct proxy *curproxy, + const struct sockaddr_storage *addr, + struct http_ext_7239_forby *forby) +{ + struct in6_addr *ip6_addr; + + if (forby->nn_mode == HTTP_7239_FORBY_ORIG) { + if (addr && addr->ss_family == AF_INET) { + unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)addr)->sin_addr; + + chunk_appendf(out, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]); + } + else if (addr && addr->ss_family == AF_INET6) { + ip6_addr = &((struct sockaddr_in6 *)addr)->sin6_addr; + print_ip6: + { + char pn[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, + ip6_addr, + pn, sizeof(pn)); + if (!forby->np_mode) + chunk_appendf(out, "\""); /* explicit quoting required for ipv6 */ + chunk_appendf(out, "[%s]", pn); + } + } + /* else: not supported */ + } + else if (forby->nn_mode == HTTP_7239_FORBY_SMP && forby->nn_expr) { + struct sample *smp; + + smp = sample_process(curproxy, s->sess, s, + SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->nn_expr, NULL); + + if (smp) { + if (smp->data.type == SMP_T_IPV6) { + /* smp is valid IP6, print with RFC compliant output */ + ip6_addr = &smp->data.u.ipv6; + goto print_ip6; + } + if (sample_casts[smp->data.type][SMP_T_STR] && + sample_casts[smp->data.type][SMP_T_STR](smp)) { + struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data); + struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data); + struct forwarded_header_nodename nodename; + + /* validate nodename */ + if (http_7239_extract_nodename(&validate_n, &nodename, 1) && + !istlen(validate_n)) { + if (nodename.type == FORWARDED_HEADER_IP && + nodename.ip.ss_family == AF_INET6) { + /* special care needed for valid ip6 nodename (quoting) */ + ip6_addr = &((struct sockaddr_in6 *)&nodename.ip)->sin6_addr; + goto print_ip6; + } + /* no special care needed, input is already rfc compliant, + * just print as regular non quoted string + */ + chunk_cat(out, &smp->data.u.str); + } + else if (http_7239_extract_obfs(&validate_o, NULL) && + !istlen(validate_o)) { + /* raw user input that should be printed as 7239 obfs */ + chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area); + } + /* else: not compliant */ + } + /* else: cannot be casted to str */ + } + /* else: smp error */ + } +} + +static inline void http_build_7239_header_nodeport(struct buffer *out, + struct stream *s, struct proxy *curproxy, + const struct sockaddr_storage *addr, + struct http_ext_7239_forby *forby) +{ + if (forby->np_mode == HTTP_7239_FORBY_ORIG) { + if (addr && addr->ss_family == AF_INET) + chunk_appendf(out, "%d", ntohs(((struct sockaddr_in *)addr)->sin_port)); + else if (addr && addr->ss_family == AF_INET6) + chunk_appendf(out, "%d", ntohs(((struct sockaddr_in6 *)addr)->sin6_port)); + /* else: not supported */ + } + else if (forby->np_mode == HTTP_7239_FORBY_SMP && forby->np_expr) { + struct sample *smp; + + smp = sample_fetch_as_type(curproxy, s->sess, s, + SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->np_expr, SMP_T_STR); + if (smp) { + struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data); + struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data); + + /* validate nodeport */ + if (http_7239_extract_nodeport(&validate_n, NULL) && + !istlen(validate_n)) { + /* no special care needed, input is already rfc compliant, + * just print as regular non quoted string + */ + chunk_cat(out, &smp->data.u.str); + } + else if (http_7239_extract_obfs(&validate_o, NULL) && + !istlen(validate_o)) { + /* raw user input that should be printed as 7239 obfs */ + chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area); + } + /* else: not compliant */ + } + /* else: smp error */ + } +} + +static inline void http_build_7239_header_node(struct buffer *out, + struct stream *s, struct proxy *curproxy, + const struct sockaddr_storage *addr, + struct http_ext_7239_forby *forby) +{ + size_t offset_start; + size_t offset_save; + + offset_start = out->data; + 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); + if (offset_save == out->data) { + /* could not build nodename, either because some + * data is not available or user is providing bad input + */ + chunk_appendf(out, "unknown"); + } + 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); + if (offset_save == out->data) { + /* could not build nodeport, either because some data is + * not available or user is providing bad input + */ + out->data = offset_save - 1; + } + } + if (out->data != offset_start && out->area[offset_start] == '"') + chunk_appendf(out, "\""); /* add matching end quote */ +} + +static inline void http_build_7239_header_host(struct buffer *out, + struct stream *s, struct proxy *curproxy, + struct htx *htx, struct http_ext_7239_host *host) +{ + struct http_hdr_ctx ctx = { .blk = NULL }; + char *str = NULL; + int str_len = 0; + + if (host->mode == HTTP_7239_HOST_ORIG && + http_find_header(htx, ist("host"), &ctx, 0)) { + str = ctx.value.ptr; + str_len = ctx.value.len; + print_host: + { + struct ist validate = ist2(str, str_len); + /* host check, to ensure rfc compliant output + * (assumming host is quoted/escaped) + */ + if (http_7239_extract_host(&validate, NULL, 1) && !istlen(validate)) + chunk_memcat(out, str, str_len); + /* else: not compliant or partially compliant */ + } + + } + else if (host->mode == HTTP_7239_HOST_SMP && host->expr) { + struct sample *smp; + + smp = sample_fetch_as_type(curproxy, s->sess, s, + SMP_OPT_DIR_REQ | SMP_OPT_FINAL, host->expr, SMP_T_STR); + if (smp) { + str = smp->data.u.str.area; + str_len = smp->data.u.str.data; + goto print_host; + } + /* else: smp error */ + } +} + +/* Tries build 7239 header according to parameters and context + * It both depends on ->http_ext->fwd for config and for request + * context data. + * The function will write output to buffer + * Returns 1 for success and 0 for error (ie: not enough space in buffer) + */ +static int http_build_7239_header(struct buffer *out, + struct stream *s, struct proxy *curproxy, struct htx *htx) +{ + struct connection *cli_conn = objt_conn(strm_sess(s)->origin); + + if (curproxy->http.fwd.p_proto) { + chunk_appendf(out, "%sproto=%s", ((out->data) ? ";" : ""), + ((conn_is_ssl(cli_conn)) ? "https" : "http")); + } + if (curproxy->http.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); + chunk_appendf(out, "\""); + } + + if (curproxy->http.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); + } + + if (curproxy->http.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); + } + if (unlikely(out->data == out->size)) { + /* not enough space in buffer, error */ + return 0; + } + return 1; +} + +/* This function will try to inject 7239 forwarded header + * 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 */ + + /* 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; + } + 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; +} + +/* + * =========== CONFIG =========== + * below are helpers to parse http ext options from the config + */ +static int proxy_http_parse_oom(const char *file, int linenum) +{ + int err_code = 0; + + ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + return err_code; +} + +static inline int _proxy_http_parse_7239_expr(char **args, int *cur_arg, + const char *file, int linenum, + char **expr_s) +{ + int err_code = 0; + + if (!*args[*cur_arg + 1]) { + ha_alert("parsing [%s:%d]: '%s' expects as argument.\n", + file, linenum, args[*cur_arg]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + *cur_arg += 1; + ha_free(expr_s); + *expr_s = strdup(args[*cur_arg]); + if (!*expr_s) + return proxy_http_parse_oom(file, linenum); + *cur_arg += 1; + out: + return err_code; +} + +int proxy_http_parse_7239(char **args, int cur_arg, + struct proxy *curproxy, const struct proxy *defpx, + const char *file, int linenum) +{ + int err_code = 0; + + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, "option forwarded", NULL)) { + /* option is ignored for frontends */ + err_code |= ERR_WARN; + 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; + + /* 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 */ + 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; + cur_arg += 1; + } else if (strcmp(args[cur_arg], "host") == 0) { + curproxy->http.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; + err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum, + &curproxy->http.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; + cur_arg += 1; + } else if (strcmp(args[cur_arg], "by-expr") == 0) { + curproxy->http.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); + 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; + cur_arg += 1; + } else if (strcmp(args[cur_arg], "for-expr") == 0) { + curproxy->http.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); + 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; + cur_arg += 1; + } else if (strcmp(args[cur_arg], "by_port-expr") == 0) { + curproxy->http.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); + 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; + cur_arg += 1; + } else if (strcmp(args[cur_arg], "for_port-expr") == 0) { + curproxy->http.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); + if (err_code & ERR_FATAL) + goto out; + } else { + /* unknown suboption - catchall */ + ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'proto', 'host', " + "'host-expr', 'by', 'by-expr', 'by_port', 'by_port-expr', " + "'for', 'for-expr', 'for_port' and 'for_port-expr'.\n", + file, linenum, args[0], args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } /* 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); + 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")); + 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); + 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")); + err_code |= ERR_WARN; + } + + out: + return err_code; +} + +/* returns 0 for success and positive value + * (equals to number of errors) in case of error + */ +int proxy_http_compile_7239(struct proxy *curproxy) +{ + char *err = NULL; + 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 */ + goto out; + } + + 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; + + for (loop = 0; loop < 5; loop++) { + char *expr_str = NULL; + struct sample_expr **expr = NULL; + int smp = 0; + int idx = 0; + + 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); + 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); + 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); + 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); + 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); + break; + } + if (!smp || !expr_str) + continue; /* no expr */ + /* expr cannot be NULL past this point */ + ALREADY_CHECKED(expr); + + *expr = + sample_parse_expr((char*[]){expr_str, NULL}, &idx, + curproxy->http.fwd.c_file, + curproxy->http.fwd.c_line, + &err, &curproxy->conf.args, NULL); + + if (!*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, + expr_str, err); + ha_free(&err); + cfgerr++; + } + } + curproxy->conf.args.file = NULL; + curproxy->conf.args.line = 0; + + out: + return cfgerr; +} + +/* + * =========== MGMT =========== + * below are helpers to manage http ext options + */ + +void http_ext_7239_clean(struct http_ext_7239 *clean) +{ + ha_free(&clean->c_file); + ha_free(&clean->p_host.expr_s); + ha_free(&clean->p_by.nn_expr_s); + ha_free(&clean->p_by.np_expr_s); + ha_free(&clean->p_for.nn_expr_s); + ha_free(&clean->p_for.np_expr_s); + + release_sample_expr(clean->p_host.expr); + clean->p_host.expr = NULL; + release_sample_expr(clean->p_by.nn_expr); + clean->p_by.nn_expr = NULL; + release_sample_expr(clean->p_by.np_expr); + clean->p_by.np_expr = NULL; + release_sample_expr(clean->p_for.nn_expr); + clean->p_for.nn_expr = NULL; + release_sample_expr(clean->p_for.np_expr); + clean->p_for.np_expr = NULL; +} + +void http_ext_7239_copy(struct http_ext_7239 *dest, const struct http_ext_7239 *orig) +{ + if (orig->c_file) + dest->c_file = strdup(orig->c_file); + dest->c_line = orig->c_line; + /* proto */ + dest->p_proto = orig->p_proto; + /* host */ + dest->p_host.mode = orig->p_host.mode; + if (orig->p_host.expr_s) + dest->p_host.expr_s = strdup(orig->p_host.expr_s); + /* by - nodename */ + dest->p_by.nn_mode = orig->p_by.nn_mode; + if (orig->p_by.nn_expr_s) + dest->p_by.nn_expr_s = strdup(orig->p_by.nn_expr_s); + /* by - nodeport */ + dest->p_by.np_mode = orig->p_by.np_mode; + if (orig->p_by.np_expr_s) + dest->p_by.np_expr_s = strdup(orig->p_by.np_expr_s); + /* for - nodename */ + dest->p_for.nn_mode = orig->p_for.nn_mode; + if (orig->p_for.nn_expr_s) + dest->p_for.nn_expr_s = strdup(orig->p_for.nn_expr_s); + /* for - nodeport */ + dest->p_for.np_mode = orig->p_for.np_mode; + if (orig->p_for.np_expr_s) + dest->p_for.np_expr_s = strdup(orig->p_for.np_expr_s); +} diff --git a/src/proxy.c b/src/proxy.c index 4696dec7ca..9166a324ce 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -353,6 +354,7 @@ void free_proxy(struct proxy *p) free(p->desc); istfree(&p->fwdfor_hdr_name); istfree(&p->orgto_hdr_name); + http_ext_7239_clean(&p->http.fwd); task_destroy(p->task); @@ -1467,6 +1469,8 @@ void proxy_free_defaults(struct proxy *defproxy) istfree(&defproxy->orgto_hdr_name); istfree(&defproxy->server_id_hdr_name); + http_ext_7239_clean(&defproxy->http.fwd); + list_for_each_entry_safe(acl, aclb, &defproxy->acl, list) { LIST_DELETE(&acl->list); prune_acl(acl); @@ -1696,6 +1700,9 @@ 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); curproxy->cookie_len = defproxy->cookie_len;