From b2bb9257d255e5d5377d9d3ce5b2d41795207669 Mon Sep 17 00:00:00 2001 From: Aurelien DARRAGON Date: Wed, 28 Dec 2022 15:37:57 +0100 Subject: [PATCH] MINOR: proxy/http_ext: introduce proxy forwarded option Introducing http_ext class for http extension related work that doesn't fit into existing http classes. HTTP extension "forwarded", introduced with 7239 RFC is now supported by haproxy. The option supports various modes from simple to complex usages involving custom sample expressions. 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 By default (no argument provided), forwarded option will try to mimic x-forward-for common setups (source client ip address + source protocol) The option is not available for frontends. no option forwarded is supported. More info about 7239 RFC here: https://www.rfc-editor.org/rfc/rfc7239.html More info about the feature in doc/configuration.txt This should address feature request GH #575 Depends on: - "MINOR: http_htx: add http_append_header() to append value to header" - "MINOR: sample: add ARGC_OPT" - "MINOR: proxy: introduce http only options" --- Makefile | 2 +- doc/configuration.txt | 135 +++++ include/haproxy/http_ext-t.h | 113 ++++ include/haproxy/http_ext.h | 41 ++ include/haproxy/proxy-t.h | 5 +- src/cfgparse-listen.c | 13 +- src/cfgparse.c | 11 + src/http_ana.c | 7 + src/http_ext.c | 1059 ++++++++++++++++++++++++++++++++++ src/proxy.c | 7 + 10 files changed, 1390 insertions(+), 3 deletions(-) create mode 100644 include/haproxy/http_ext-t.h create mode 100644 include/haproxy/http_ext.h create mode 100644 src/http_ext.c diff --git a/Makefile b/Makefile index 933a54da8..4dc52c1c3 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 aa4295686..2d70047f0 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 000000000..b5b22206f --- /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 000000000..481298cc1 --- /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 e863299cf..395df2d6a 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 258564404..66a0dc5ef 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 @@ stats_error_parsing: 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 6f2d93a05..15e22968b 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -3669,6 +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) + 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 @@ 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_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 1d9423af4..bbc6f4dc1 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 000000000..fcb5a07bc --- /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 4696dec7c..9166a324c 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;