You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2017/10/02 19:50:52 UTC
[trafficserver] branch master updated: Add support for Forwarded
HTTP header tag (RFC7239).
This is an automated email from the ASF dual-hosted git repository.
amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new d91662d Add support for Forwarded HTTP header tag (RFC7239).
d91662d is described below
commit d91662dafb1d826603769ba6202d502789ee49b6
Author: Walt Karas <wk...@yahoo-inc.com>
AuthorDate: Fri Aug 4 23:58:13 2017 +0000
Add support for Forwarded HTTP header tag (RFC7239).
---
.../transparent-forward-proxying.en.rst | 3 +
doc/admin-guide/files/records.config.en.rst | 30 +++
.../api/functions/TSHttpOverridableConfig.en.rst | 1 +
lib/ts/apidefs.h.in | 3 +
mgmt/RecordsConfig.cc | 2 +
.../header_normalize/header_normalize.cc | 1 +
plugins/experimental/ts_lua/ts_lua_http_config.c | 2 +
plugins/s3_auth/aws_auth_v4.cc | 1 +
plugins/s3_auth/s3_auth.cc | 1 +
proxy/InkAPI.cc | 21 ++
proxy/InkAPITest.cc | 227 ++++++++--------
proxy/hdrs/HdrToken.cc | 12 +-
proxy/hdrs/MIME.cc | 6 +
proxy/hdrs/MIME.h | 2 +
proxy/http/ForwardedConfig.cc | 189 ++++++++++++++
proxy/http/HttpConfig.cc | 43 +++
proxy/http/HttpConfig.h | 37 +++
proxy/http/HttpTransact.cc | 1 +
proxy/http/HttpTransactHeaders.cc | 265 ++++++++++++++++---
proxy/http/HttpTransactHeaders.h | 7 +
proxy/http/Makefile.am | 17 +-
proxy/http/unit-tests/sym-links/MemView.cc | 1 +
proxy/http/unit-tests/test_ForwardedConfig.cc | 169 ++++++++++++
.../http/unit-tests/test_ForwardedConfig_mocks.cc | 86 ++++++
tests/gold_tests/headers/forwarded-observer.py | 63 +++++
tests/gold_tests/headers/forwarded.gold | 41 +++
tests/gold_tests/headers/forwarded.test.py | 289 +++++++++++++++++++++
27 files changed, 1372 insertions(+), 148 deletions(-)
diff --git a/doc/admin-guide/configuration/transparent-forward-proxying.en.rst b/doc/admin-guide/configuration/transparent-forward-proxying.en.rst
index cee8ba6..d573c28 100644
--- a/doc/admin-guide/configuration/transparent-forward-proxying.en.rst
+++ b/doc/admin-guide/configuration/transparent-forward-proxying.en.rst
@@ -88,6 +88,9 @@ You may also want to consider some of these configuration options:
- The client request header X-Forwarded-For may be toggled with
:ts:cv:`proxy.config.http.insert_squid_x_forwarded_for`.
+- The client request header Forwarded may be configured with
+ :ts:cv:`proxy.config.http.insert_forwarded`.
+
Client Configuration
====================
diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst
index 0aaf67e..cf1b921 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -1666,6 +1666,36 @@ Proxy User Variables
When enabled (``1``), Traffic Server adds the client IP address to the ``X-Forwarded-For`` header.
+.. ts:cv:: CONFIG proxy.config.http.insert_forwarded STRING none
+ :reloadable:
+ :overridable:
+
+ The default value (``none``) means that Traffic Server does not insert or append information to any
+ ``Forwarded`` header (described in IETF RFC 7239) in the request message. To put information into a
+ ``Forwarded`` header in the request, the value of this variable must be a list of the ``Forwarded``
+ parameters to be inserted.
+
+ ================== ===============================================================
+ Parameter Value of parameter place in outgoing Forwarded header
+ ================== ===============================================================
+ for Client IP address
+ by=ip Proxy IP address
+ by=unknown The literal string ``unknown``
+ by=servername Proxy server name
+ by=uuid Server UUID prefixed with ``_``
+ proto Protocol of incoming request
+ host The host specified in the incoming request
+ connection=compact Connection with basic transaction codes.
+ connection=std Connection with detailed transaction codes.
+ connection=full Full user agent connection :ref:`protocol tags <protocol_tags>`
+ ================== ===============================================================
+
+ Each paramater in the list must be separated by ``|`` or ``:``. For example, ``for|by=uuid|proto`` is
+ a valid value for this variable. Note that the ``connection`` parameter is a non-standard extension to
+ RFC 7239. Also note that, while Traffic Server allows multiple ``by`` parameters for the same proxy, this
+ is prohibited by RFC 7239. Currently, for the ``host`` parameter to provide the original host from the
+ incoming client request, `proxy.config.url_remap.pristine_host_hdr`_ must be enabled.
+
.. ts:cv:: CONFIG proxy.config.http.normalize_ae INT 1
:reloadable:
:overridable:
diff --git a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
index f8f191e..e1340ad 100644
--- a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
+++ b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
@@ -123,6 +123,7 @@ c:member:`TS_CONFIG_HTTP_INSERT_AGE_IN_RESPONSE` :ts:cv:`prox
c:member:`TS_CONFIG_HTTP_INSERT_REQUEST_VIA_STR` :ts:cv:`proxy.config.http.insert_request_via_str`
c:member:`TS_CONFIG_HTTP_INSERT_RESPONSE_VIA_STR` :ts:cv:`proxy.config.http.insert_response_via_str`
c:member:`TS_CONFIG_HTTP_INSERT_SQUID_X_FORWARDED_FOR` :ts:cv:`proxy.config.http.insert_squid_x_forwarded_for`
+c:member:`TS_CONFIG_HTTP_INSERT_FORWARDED` :ts:cv:`proxy.config.http.insert_forwarded`
c:member:`TS_CONFIG_HTTP_KEEP_ALIVE_ENABLED_IN` :ts:cv:`proxy.config.http.keep_alive_enabled_in`
c:member:`TS_CONFIG_HTTP_KEEP_ALIVE_ENABLED_OUT` :ts:cv:`proxy.config.http.keep_alive_enabled_out`
c:member:`TS_CONFIG_HTTP_KEEP_ALIVE_NO_ACTIVITY_TlMEOUT_IN` :ts:cv:`proxy.config.http.keep_alive_no_activity_timeout_in`
diff --git a/lib/ts/apidefs.h.in b/lib/ts/apidefs.h.in
index 0765cee..a9bfa8c 100644
--- a/lib/ts/apidefs.h.in
+++ b/lib/ts/apidefs.h.in
@@ -756,6 +756,7 @@ typedef enum {
TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS,
TS_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT,
TS_CONFIG_HTTP_NORMALIZE_AE,
+ TS_CONFIG_HTTP_INSERT_FORWARDED,
TS_CONFIG_LAST_ENTRY
} TSOverridableConfigKey;
@@ -1008,6 +1009,7 @@ extern tsapi const char *TS_MIME_FIELD_WARNING;
extern tsapi const char *TS_MIME_FIELD_WWW_AUTHENTICATE;
extern tsapi const char *TS_MIME_FIELD_XREF;
extern tsapi const char *TS_MIME_FIELD_X_FORWARDED_FOR;
+extern tsapi const char *TS_MIME_FIELD_FORWARDED;
/* --------------------------------------------------------------------------
MIME fields string lengths */
@@ -1083,6 +1085,7 @@ extern tsapi int TS_MIME_LEN_WARNING;
extern tsapi int TS_MIME_LEN_WWW_AUTHENTICATE;
extern tsapi int TS_MIME_LEN_XREF;
extern tsapi int TS_MIME_LEN_X_FORWARDED_FOR;
+extern tsapi int TS_MIME_LEN_FORWARDED;
/* --------------------------------------------------------------------------
HTTP values */
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index 74010e2..6868337 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -560,6 +560,8 @@ static const RecordElement RecordsConfig[] =
,
{RECT_CONFIG, "proxy.config.http.insert_squid_x_forwarded_for", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
,
+ {RECT_CONFIG, "proxy.config.http.insert_forwarded", RECD_STRING, "none", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+ ,
{RECT_CONFIG, "proxy.config.http.insert_age_in_response", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
,
{RECT_CONFIG, "proxy.config.http.enable_http_stats", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
diff --git a/plugins/experimental/header_normalize/header_normalize.cc b/plugins/experimental/header_normalize/header_normalize.cc
index c4d9e6b..fc99f89 100644
--- a/plugins/experimental/header_normalize/header_normalize.cc
+++ b/plugins/experimental/header_normalize/header_normalize.cc
@@ -135,6 +135,7 @@ buildHdrMap()
hdrMap["xref"] = "Xref";
hdrMap["x-id"] = "X-ID";
hdrMap["x-forwarded-for"] = "X-Forwarded-For";
+ hdrMap["forwarded"] = "Forwarded";
hdrMap["sec-websocket-key"] = "Sec-WebSocket-Key";
hdrMap["sec-websocket-version"] = "Sec-WebSocket-Version";
}
diff --git a/plugins/experimental/ts_lua/ts_lua_http_config.c b/plugins/experimental/ts_lua/ts_lua_http_config.c
index 8b6f8e2..1a57d79 100644
--- a/plugins/experimental/ts_lua/ts_lua_http_config.c
+++ b/plugins/experimental/ts_lua/ts_lua_http_config.c
@@ -40,6 +40,7 @@ typedef enum {
TS_LUA_CONFIG_HTTP_ANONYMIZE_INSERT_CLIENT_IP = TS_CONFIG_HTTP_ANONYMIZE_INSERT_CLIENT_IP,
TS_LUA_CONFIG_HTTP_RESPONSE_SERVER_ENABLED = TS_CONFIG_HTTP_RESPONSE_SERVER_ENABLED,
TS_LUA_CONFIG_HTTP_INSERT_SQUID_X_FORWARDED_FOR = TS_CONFIG_HTTP_INSERT_SQUID_X_FORWARDED_FOR,
+ TS_LUA_CONFIG_HTTP_INSERT_FORWARDED = TS_CONFIG_HTTP_INSERT_FORWARDED,
TS_LUA_CONFIG_HTTP_SERVER_TCP_INIT_CWND = TS_CONFIG_HTTP_SERVER_TCP_INIT_CWND,
TS_LUA_CONFIG_HTTP_SEND_HTTP11_REQUESTS = TS_CONFIG_HTTP_SEND_HTTP11_REQUESTS,
TS_LUA_CONFIG_HTTP_CACHE_HTTP = TS_CONFIG_HTTP_CACHE_HTTP,
@@ -163,6 +164,7 @@ ts_lua_var_item ts_lua_http_config_vars[] = {
TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_ANONYMIZE_INSERT_CLIENT_IP),
TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_RESPONSE_SERVER_ENABLED),
TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_INSERT_SQUID_X_FORWARDED_FOR),
+ TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_INSERT_FORWARDED),
TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_SERVER_TCP_INIT_CWND),
TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_SEND_HTTP11_REQUESTS),
TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CACHE_HTTP),
diff --git a/plugins/s3_auth/aws_auth_v4.cc b/plugins/s3_auth/aws_auth_v4.cc
index f522118..5c3cd66 100644
--- a/plugins/s3_auth/aws_auth_v4.cc
+++ b/plugins/s3_auth/aws_auth_v4.cc
@@ -470,6 +470,7 @@ createDefaultExcludeHeaders()
StringSet m;
/* exclude headers that are meant to be changed */
m.insert("x-forwarded-for");
+ m.insert("forwarded");
m.insert("via");
return m;
}
diff --git a/plugins/s3_auth/s3_auth.cc b/plugins/s3_auth/s3_auth.cc
index 2172170..97e7d8d 100644
--- a/plugins/s3_auth/s3_auth.cc
+++ b/plugins/s3_auth/s3_auth.cc
@@ -364,6 +364,7 @@ public:
/* Exclude headers that are meant to be changed */
_v4excludeHeaders.insert("x-forwarded-for");
+ _v4excludeHeaders.insert("forwarded");
_v4excludeHeaders.insert("via");
}
diff --git a/proxy/InkAPI.cc b/proxy/InkAPI.cc
index 5114cc4..a710a5e 100644
--- a/proxy/InkAPI.cc
+++ b/proxy/InkAPI.cc
@@ -216,6 +216,7 @@ tsapi const char *TS_MIME_FIELD_WARNING;
tsapi const char *TS_MIME_FIELD_WWW_AUTHENTICATE;
tsapi const char *TS_MIME_FIELD_XREF;
tsapi const char *TS_MIME_FIELD_X_FORWARDED_FOR;
+tsapi const char *TS_MIME_FIELD_FORWARDED;
/* MIME fields string lengths */
tsapi int TS_MIME_LEN_ACCEPT;
@@ -290,6 +291,7 @@ tsapi int TS_MIME_LEN_WARNING;
tsapi int TS_MIME_LEN_WWW_AUTHENTICATE;
tsapi int TS_MIME_LEN_XREF;
tsapi int TS_MIME_LEN_X_FORWARDED_FOR;
+tsapi int TS_MIME_LEN_FORWARDED;
/* HTTP miscellaneous values */
tsapi const char *TS_HTTP_VALUE_BYTES;
@@ -1500,6 +1502,7 @@ api_init()
TS_MIME_FIELD_WWW_AUTHENTICATE = MIME_FIELD_WWW_AUTHENTICATE;
TS_MIME_FIELD_XREF = MIME_FIELD_XREF;
TS_MIME_FIELD_X_FORWARDED_FOR = MIME_FIELD_X_FORWARDED_FOR;
+ TS_MIME_FIELD_FORWARDED = MIME_FIELD_FORWARDED;
TS_MIME_LEN_ACCEPT = MIME_LEN_ACCEPT;
TS_MIME_LEN_ACCEPT_CHARSET = MIME_LEN_ACCEPT_CHARSET;
@@ -1573,6 +1576,7 @@ api_init()
TS_MIME_LEN_WWW_AUTHENTICATE = MIME_LEN_WWW_AUTHENTICATE;
TS_MIME_LEN_XREF = MIME_LEN_XREF;
TS_MIME_LEN_X_FORWARDED_FOR = MIME_LEN_X_FORWARDED_FOR;
+ TS_MIME_LEN_FORWARDED = MIME_LEN_FORWARDED;
/* HTTP methods */
TS_HTTP_METHOD_CONNECT = HTTP_METHOD_CONNECT;
@@ -7841,6 +7845,9 @@ _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overr
case TS_CONFIG_HTTP_INSERT_SQUID_X_FORWARDED_FOR:
ret = _memberp_to_generic(&overridableHttpConfig->insert_squid_x_forwarded_for, typep);
break;
+ case TS_CONFIG_HTTP_INSERT_FORWARDED:
+ ret = _memberp_to_generic(&overridableHttpConfig->insert_forwarded, typep);
+ break;
case TS_CONFIG_HTTP_SERVER_TCP_INIT_CWND:
ret = _memberp_to_generic(&overridableHttpConfig->server_tcp_init_cwnd, typep);
break;
@@ -8265,6 +8272,17 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char
s->t_state.txn_conf->client_cert_filepath = const_cast<char *>(value);
}
break;
+ case TS_CONFIG_HTTP_INSERT_FORWARDED:
+ if (value && length > 0) {
+ ts::LocalBufferWriter<1024> error;
+ HttpForwarded::OptionBitSet bs = HttpForwarded::optStrToBitset(ts::string_view(value, length), error);
+ if (!error.size()) {
+ s->t_state.txn_conf->insert_forwarded = bs;
+ } else {
+ Error("HTTP %.*s", static_cast<int>(error.size()), error.data());
+ }
+ }
+ break;
default:
return TS_ERROR;
break;
@@ -8361,6 +8379,9 @@ TSHttpTxnConfigFind(const char *name, int length, TSOverridableConfigKey *conf,
cnf = TS_CONFIG_HTTP_CACHE_GENERATION;
} else if (!strncmp(name, "proxy.config.http.insert_client_ip", length)) {
cnf = TS_CONFIG_HTTP_ANONYMIZE_INSERT_CLIENT_IP;
+ } else if (!strncmp(name, "proxy.config.http.insert_forwarded", length)) {
+ cnf = TS_CONFIG_HTTP_INSERT_FORWARDED;
+ typ = TS_RECORDDATATYPE_STRING;
}
break;
diff --git a/proxy/InkAPITest.cc b/proxy/InkAPITest.cc
index c7ce69b..bc97c62 100644
--- a/proxy/InkAPITest.cc
+++ b/proxy/InkAPITest.cc
@@ -7477,120 +7477,119 @@ EXCLUSIVE_REGRESSION_TEST(SDK_API_TSHttpConnectServerIntercept)(RegressionTest *
////////////////////////////////////////////////
// The order of these should be the same as TSOverridableConfigKey
-const char *SDK_Overridable_Configs[TS_CONFIG_LAST_ENTRY] = {
- "proxy.config.url_remap.pristine_host_hdr",
- "proxy.config.http.chunking_enabled",
- "proxy.config.http.negative_caching_enabled",
- "proxy.config.http.negative_caching_lifetime",
- "proxy.config.http.cache.when_to_revalidate",
- "proxy.config.http.keep_alive_enabled_in",
- "proxy.config.http.keep_alive_enabled_out",
- "proxy.config.http.keep_alive_post_out",
- "proxy.config.http.server_session_sharing.match",
- "proxy.config.net.sock_recv_buffer_size_out",
- "proxy.config.net.sock_send_buffer_size_out",
- "proxy.config.net.sock_option_flag_out",
- "proxy.config.http.forward.proxy_auth_to_parent",
- "proxy.config.http.anonymize_remove_from",
- "proxy.config.http.anonymize_remove_referer",
- "proxy.config.http.anonymize_remove_user_agent",
- "proxy.config.http.anonymize_remove_cookie",
- "proxy.config.http.anonymize_remove_client_ip",
- "proxy.config.http.insert_client_ip",
- "proxy.config.http.response_server_enabled",
- "proxy.config.http.insert_squid_x_forwarded_for",
- "proxy.config.http.server_tcp_init_cwnd",
- "proxy.config.http.send_http11_requests",
- "proxy.config.http.cache.http",
- "proxy.config.http.cache.ignore_client_no_cache",
- "proxy.config.http.cache.ignore_client_cc_max_age",
- "proxy.config.http.cache.ims_on_client_no_cache",
- "proxy.config.http.cache.ignore_server_no_cache",
- "proxy.config.http.cache.cache_responses_to_cookies",
- "proxy.config.http.cache.ignore_authentication",
- "proxy.config.http.cache.cache_urls_that_look_dynamic",
- "proxy.config.http.cache.required_headers",
- "proxy.config.http.insert_request_via_str",
- "proxy.config.http.insert_response_via_str",
- "proxy.config.http.cache.heuristic_min_lifetime",
- "proxy.config.http.cache.heuristic_max_lifetime",
- "proxy.config.http.cache.guaranteed_min_lifetime",
- "proxy.config.http.cache.guaranteed_max_lifetime",
- "proxy.config.http.cache.max_stale_age",
- "proxy.config.http.keep_alive_no_activity_timeout_in",
- "proxy.config.http.keep_alive_no_activity_timeout_out",
- "proxy.config.http.transaction_no_activity_timeout_in",
- "proxy.config.http.transaction_no_activity_timeout_out",
- "proxy.config.http.transaction_active_timeout_out",
- "proxy.config.http.origin_max_connections",
- "proxy.config.http.connect_attempts_max_retries",
- "proxy.config.http.connect_attempts_max_retries_dead_server",
- "proxy.config.http.connect_attempts_rr_retries",
- "proxy.config.http.connect_attempts_timeout",
- "proxy.config.http.post_connect_attempts_timeout",
- "proxy.config.http.down_server.cache_time",
- "proxy.config.http.down_server.abort_threshold",
- "proxy.config.http.doc_in_cache_skip_dns",
- "proxy.config.http.background_fill_active_timeout",
- "proxy.config.http.response_server_str",
- "proxy.config.http.cache.heuristic_lm_factor",
- "proxy.config.http.background_fill_completed_threshold",
- "proxy.config.net.sock_packet_mark_out",
- "proxy.config.net.sock_packet_tos_out",
- "proxy.config.http.insert_age_in_response",
- "proxy.config.http.chunking.size",
- "proxy.config.http.flow_control.enabled",
- "proxy.config.http.flow_control.low_water",
- "proxy.config.http.flow_control.high_water",
- "proxy.config.http.cache.range.lookup",
- "proxy.config.http.default_buffer_size",
- "proxy.config.http.default_buffer_water_mark",
- "proxy.config.http.request_header_max_size",
- "proxy.config.http.response_header_max_size",
- "proxy.config.http.negative_revalidating_enabled",
- "proxy.config.http.negative_revalidating_lifetime",
- "proxy.config.ssl.hsts_max_age",
- "proxy.config.ssl.hsts_include_subdomains",
- "proxy.config.http.cache.open_read_retry_time",
- "proxy.config.http.cache.max_open_read_retries",
- "proxy.config.http.cache.range.write",
- "proxy.config.http.post.check.content_length.enabled",
- "proxy.config.http.global_user_agent_header",
- "proxy.config.http.auth_server_session_private",
- "proxy.config.http.slow.log.threshold",
- "proxy.config.http.cache.generation",
- "proxy.config.body_factory.template_base",
- "proxy.config.http.cache.open_write_fail_action",
- "proxy.config.http.number_of_redirections",
- "proxy.config.http.cache.max_open_write_retries",
- "proxy.config.http.redirect_use_orig_cache_key",
- "proxy.config.http.attach_server_session_to_client",
- "proxy.config.http.origin_max_connections_queue",
- "proxy.config.websocket.no_activity_timeout",
- "proxy.config.websocket.active_timeout",
- "proxy.config.http.uncacheable_requests_bypass_parent",
- "proxy.config.http.parent_proxy.total_connect_attempts",
- "proxy.config.http.transaction_active_timeout_in",
- "proxy.config.srv_enabled",
- "proxy.config.http.forward_connect_method",
- "proxy.config.ssl.client.cert.filename",
- "proxy.config.ssl.client.cert.path",
- "proxy.config.http.parent_proxy.mark_down_hostdb",
- "proxy.config.ssl.client.verify.server",
- "proxy.config.http.cache.enable_default_vary_headers",
- "proxy.config.http.cache.vary_default_text",
- "proxy.config.http.cache.vary_default_images",
- "proxy.config.http.cache.vary_default_other",
- "proxy.config.http.cache.ignore_accept_mismatch",
- "proxy.config.http.cache.ignore_accept_language_mismatch",
- "proxy.config.http.cache.ignore_accept_encoding_mismatch",
- "proxy.config.http.cache.ignore_accept_charset_mismatch",
- "proxy.config.http.parent_proxy.fail_threshold",
- "proxy.config.http.parent_proxy.retry_time",
- "proxy.config.http.parent_proxy.per_parent_connect_attempts",
- "proxy.config.http.parent_proxy.connect_attempts_timeout",
- "proxy.config.http.normalize_ae",
-};
+const char *SDK_Overridable_Configs[TS_CONFIG_LAST_ENTRY] = {"proxy.config.url_remap.pristine_host_hdr",
+ "proxy.config.http.chunking_enabled",
+ "proxy.config.http.negative_caching_enabled",
+ "proxy.config.http.negative_caching_lifetime",
+ "proxy.config.http.cache.when_to_revalidate",
+ "proxy.config.http.keep_alive_enabled_in",
+ "proxy.config.http.keep_alive_enabled_out",
+ "proxy.config.http.keep_alive_post_out",
+ "proxy.config.http.server_session_sharing.match",
+ "proxy.config.net.sock_recv_buffer_size_out",
+ "proxy.config.net.sock_send_buffer_size_out",
+ "proxy.config.net.sock_option_flag_out",
+ "proxy.config.http.forward.proxy_auth_to_parent",
+ "proxy.config.http.anonymize_remove_from",
+ "proxy.config.http.anonymize_remove_referer",
+ "proxy.config.http.anonymize_remove_user_agent",
+ "proxy.config.http.anonymize_remove_cookie",
+ "proxy.config.http.anonymize_remove_client_ip",
+ "proxy.config.http.insert_client_ip",
+ "proxy.config.http.response_server_enabled",
+ "proxy.config.http.insert_squid_x_forwarded_for",
+ "proxy.config.http.server_tcp_init_cwnd",
+ "proxy.config.http.send_http11_requests",
+ "proxy.config.http.cache.http",
+ "proxy.config.http.cache.ignore_client_no_cache",
+ "proxy.config.http.cache.ignore_client_cc_max_age",
+ "proxy.config.http.cache.ims_on_client_no_cache",
+ "proxy.config.http.cache.ignore_server_no_cache",
+ "proxy.config.http.cache.cache_responses_to_cookies",
+ "proxy.config.http.cache.ignore_authentication",
+ "proxy.config.http.cache.cache_urls_that_look_dynamic",
+ "proxy.config.http.cache.required_headers",
+ "proxy.config.http.insert_request_via_str",
+ "proxy.config.http.insert_response_via_str",
+ "proxy.config.http.cache.heuristic_min_lifetime",
+ "proxy.config.http.cache.heuristic_max_lifetime",
+ "proxy.config.http.cache.guaranteed_min_lifetime",
+ "proxy.config.http.cache.guaranteed_max_lifetime",
+ "proxy.config.http.cache.max_stale_age",
+ "proxy.config.http.keep_alive_no_activity_timeout_in",
+ "proxy.config.http.keep_alive_no_activity_timeout_out",
+ "proxy.config.http.transaction_no_activity_timeout_in",
+ "proxy.config.http.transaction_no_activity_timeout_out",
+ "proxy.config.http.transaction_active_timeout_out",
+ "proxy.config.http.origin_max_connections",
+ "proxy.config.http.connect_attempts_max_retries",
+ "proxy.config.http.connect_attempts_max_retries_dead_server",
+ "proxy.config.http.connect_attempts_rr_retries",
+ "proxy.config.http.connect_attempts_timeout",
+ "proxy.config.http.post_connect_attempts_timeout",
+ "proxy.config.http.down_server.cache_time",
+ "proxy.config.http.down_server.abort_threshold",
+ "proxy.config.http.doc_in_cache_skip_dns",
+ "proxy.config.http.background_fill_active_timeout",
+ "proxy.config.http.response_server_str",
+ "proxy.config.http.cache.heuristic_lm_factor",
+ "proxy.config.http.background_fill_completed_threshold",
+ "proxy.config.net.sock_packet_mark_out",
+ "proxy.config.net.sock_packet_tos_out",
+ "proxy.config.http.insert_age_in_response",
+ "proxy.config.http.chunking.size",
+ "proxy.config.http.flow_control.enabled",
+ "proxy.config.http.flow_control.low_water",
+ "proxy.config.http.flow_control.high_water",
+ "proxy.config.http.cache.range.lookup",
+ "proxy.config.http.default_buffer_size",
+ "proxy.config.http.default_buffer_water_mark",
+ "proxy.config.http.request_header_max_size",
+ "proxy.config.http.response_header_max_size",
+ "proxy.config.http.negative_revalidating_enabled",
+ "proxy.config.http.negative_revalidating_lifetime",
+ "proxy.config.ssl.hsts_max_age",
+ "proxy.config.ssl.hsts_include_subdomains",
+ "proxy.config.http.cache.open_read_retry_time",
+ "proxy.config.http.cache.max_open_read_retries",
+ "proxy.config.http.cache.range.write",
+ "proxy.config.http.post.check.content_length.enabled",
+ "proxy.config.http.global_user_agent_header",
+ "proxy.config.http.auth_server_session_private",
+ "proxy.config.http.slow.log.threshold",
+ "proxy.config.http.cache.generation",
+ "proxy.config.body_factory.template_base",
+ "proxy.config.http.cache.open_write_fail_action",
+ "proxy.config.http.number_of_redirections",
+ "proxy.config.http.cache.max_open_write_retries",
+ "proxy.config.http.redirect_use_orig_cache_key",
+ "proxy.config.http.attach_server_session_to_client",
+ "proxy.config.http.origin_max_connections_queue",
+ "proxy.config.websocket.no_activity_timeout",
+ "proxy.config.websocket.active_timeout",
+ "proxy.config.http.uncacheable_requests_bypass_parent",
+ "proxy.config.http.parent_proxy.total_connect_attempts",
+ "proxy.config.http.transaction_active_timeout_in",
+ "proxy.config.srv_enabled",
+ "proxy.config.http.forward_connect_method",
+ "proxy.config.ssl.client.cert.filename",
+ "proxy.config.ssl.client.cert.path",
+ "proxy.config.http.parent_proxy.mark_down_hostdb",
+ "proxy.config.ssl.client.verify.server",
+ "proxy.config.http.cache.enable_default_vary_headers",
+ "proxy.config.http.cache.vary_default_text",
+ "proxy.config.http.cache.vary_default_images",
+ "proxy.config.http.cache.vary_default_other",
+ "proxy.config.http.cache.ignore_accept_mismatch",
+ "proxy.config.http.cache.ignore_accept_language_mismatch",
+ "proxy.config.http.cache.ignore_accept_encoding_mismatch",
+ "proxy.config.http.cache.ignore_accept_charset_mismatch",
+ "proxy.config.http.parent_proxy.fail_threshold",
+ "proxy.config.http.parent_proxy.retry_time",
+ "proxy.config.http.parent_proxy.per_parent_connect_attempts",
+ "proxy.config.http.parent_proxy.connect_attempts_timeout",
+ "proxy.config.http.normalize_ae",
+ "proxy.config.http.insert_forwarded"};
REGRESSION_TEST(SDK_API_OVERRIDABLE_CONFIGS)(RegressionTest *test, int /* atype ATS_UNUSED */, int *pstatus)
{
diff --git a/proxy/hdrs/HdrToken.cc b/proxy/hdrs/HdrToken.cc
index 8cb1f3b..6f8064c 100644
--- a/proxy/hdrs/HdrToken.cc
+++ b/proxy/hdrs/HdrToken.cc
@@ -108,7 +108,9 @@ static const char *_hdrtoken_strs[] = {
// Header extensions
"X-ID", "X-Forwarded-For", "TE", "Strict-Transport-Security", "100-continue",
-};
+
+ // RFC-2739
+ "Forwarded"};
static HdrTokenTypeBinding _hdrtoken_strs_type_initializers[] = {
{"file", HDRTOKEN_TYPE_SCHEME},
@@ -233,6 +235,7 @@ static HdrTokenFieldInfo _hdrtoken_strs_field_initializers[] = {
{"Xref", MIME_SLOTID_NONE, MIME_PRESENCE_XREF, HTIF_NONE},
{"X-ID", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, (HTIF_COMMAS | HTIF_MULTVALS | HTIF_HOPBYHOP)},
{"X-Forwarded-For", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, (HTIF_COMMAS | HTIF_MULTVALS)},
+ {"Forwarded", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, (HTIF_COMMAS | HTIF_MULTVALS)},
{"Sec-WebSocket-Key", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HTIF_NONE},
{"Sec-WebSocket-Version", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HTIF_NONE},
{nullptr, 0, 0, 0},
@@ -291,6 +294,9 @@ hdrtoken_hash(const unsigned char *string, unsigned int length)
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
+// WARNING: Indexes into this array are stored on disk for cached objects. New strings must be added at the end of the array to
+// avoid changing the indexes of pre-existing entries, unless the cache format version number is increased.
+//
static const char *_hdrtoken_commonly_tokenized_strs[] = {
// MIME Field names
"Accept-Charset", "Accept-Encoding", "Accept-Language", "Accept-Ranges", "Accept", "Age", "Allow",
@@ -352,7 +358,9 @@ static const char *_hdrtoken_commonly_tokenized_strs[] = {
// Header extensions
"X-ID", "X-Forwarded-For", "TE", "Strict-Transport-Security", "100-continue",
-};
+
+ // RFC-2739
+ "Forwarded"};
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
diff --git a/proxy/hdrs/MIME.cc b/proxy/hdrs/MIME.cc
index e2b0c1b..18de0f6 100644
--- a/proxy/hdrs/MIME.cc
+++ b/proxy/hdrs/MIME.cc
@@ -148,6 +148,7 @@ const char *MIME_FIELD_XREF;
const char *MIME_FIELD_ATS_INTERNAL;
const char *MIME_FIELD_X_ID;
const char *MIME_FIELD_X_FORWARDED_FOR;
+const char *MIME_FIELD_FORWARDED;
const char *MIME_FIELD_SEC_WEBSOCKET_KEY;
const char *MIME_FIELD_SEC_WEBSOCKET_VERSION;
const char *MIME_FIELD_HTTP2_SETTINGS;
@@ -263,6 +264,7 @@ int MIME_LEN_XREF;
int MIME_LEN_ATS_INTERNAL;
int MIME_LEN_X_ID;
int MIME_LEN_X_FORWARDED_FOR;
+int MIME_LEN_FORWARDED;
int MIME_LEN_SEC_WEBSOCKET_KEY;
int MIME_LEN_SEC_WEBSOCKET_VERSION;
int MIME_LEN_HTTP2_SETTINGS;
@@ -341,6 +343,7 @@ int MIME_WKSIDX_XREF;
int MIME_WKSIDX_ATS_INTERNAL;
int MIME_WKSIDX_X_ID;
int MIME_WKSIDX_X_FORWARDED_FOR;
+int MIME_WKSIDX_FORWARDED;
int MIME_WKSIDX_SEC_WEBSOCKET_KEY;
int MIME_WKSIDX_SEC_WEBSOCKET_VERSION;
int MIME_WKSIDX_HTTP2_SETTINGS;
@@ -733,6 +736,7 @@ mime_init()
MIME_FIELD_ATS_INTERNAL = hdrtoken_string_to_wks("@Ats-Internal");
MIME_FIELD_X_ID = hdrtoken_string_to_wks("X-ID");
MIME_FIELD_X_FORWARDED_FOR = hdrtoken_string_to_wks("X-Forwarded-For");
+ MIME_FIELD_FORWARDED = hdrtoken_string_to_wks("Forwarded");
MIME_FIELD_SEC_WEBSOCKET_KEY = hdrtoken_string_to_wks("Sec-WebSocket-Key");
MIME_FIELD_SEC_WEBSOCKET_VERSION = hdrtoken_string_to_wks("Sec-WebSocket-Version");
@@ -813,6 +817,7 @@ mime_init()
MIME_LEN_ATS_INTERNAL = hdrtoken_wks_to_length(MIME_FIELD_ATS_INTERNAL);
MIME_LEN_X_ID = hdrtoken_wks_to_length(MIME_FIELD_X_ID);
MIME_LEN_X_FORWARDED_FOR = hdrtoken_wks_to_length(MIME_FIELD_X_FORWARDED_FOR);
+ MIME_LEN_FORWARDED = hdrtoken_wks_to_length(MIME_FIELD_FORWARDED);
MIME_LEN_SEC_WEBSOCKET_KEY = hdrtoken_wks_to_length(MIME_FIELD_SEC_WEBSOCKET_KEY);
MIME_LEN_SEC_WEBSOCKET_VERSION = hdrtoken_wks_to_length(MIME_FIELD_SEC_WEBSOCKET_VERSION);
@@ -892,6 +897,7 @@ mime_init()
MIME_WKSIDX_XREF = hdrtoken_wks_to_index(MIME_FIELD_XREF);
MIME_WKSIDX_X_ID = hdrtoken_wks_to_index(MIME_FIELD_X_ID);
MIME_WKSIDX_X_FORWARDED_FOR = hdrtoken_wks_to_index(MIME_FIELD_X_FORWARDED_FOR);
+ MIME_WKSIDX_FORWARDED = hdrtoken_wks_to_index(MIME_FIELD_FORWARDED);
MIME_WKSIDX_SEC_WEBSOCKET_KEY = hdrtoken_wks_to_index(MIME_FIELD_SEC_WEBSOCKET_KEY);
MIME_WKSIDX_SEC_WEBSOCKET_VERSION = hdrtoken_wks_to_index(MIME_FIELD_SEC_WEBSOCKET_VERSION);
MIME_WKSIDX_HTTP2_SETTINGS = hdrtoken_wks_to_index(MIME_FIELD_HTTP2_SETTINGS);
diff --git a/proxy/hdrs/MIME.h b/proxy/hdrs/MIME.h
index 7df16ec..207cb15 100644
--- a/proxy/hdrs/MIME.h
+++ b/proxy/hdrs/MIME.h
@@ -388,6 +388,7 @@ extern const char *MIME_FIELD_XREF;
extern const char *MIME_FIELD_ATS_INTERNAL;
extern const char *MIME_FIELD_X_ID;
extern const char *MIME_FIELD_X_FORWARDED_FOR;
+extern const char *MIME_FIELD_FORWARDED;
extern const char *MIME_FIELD_SEC_WEBSOCKET_KEY;
extern const char *MIME_FIELD_SEC_WEBSOCKET_VERSION;
extern const char *MIME_FIELD_HTTP2_SETTINGS;
@@ -491,6 +492,7 @@ extern int MIME_LEN_XREF;
extern int MIME_LEN_ATS_INTERNAL;
extern int MIME_LEN_X_ID;
extern int MIME_LEN_X_FORWARDED_FOR;
+extern int MIME_LEN_FORWARDED;
extern int MIME_LEN_BYTES;
extern int MIME_LEN_CHUNKED;
diff --git a/proxy/http/ForwardedConfig.cc b/proxy/http/ForwardedConfig.cc
new file mode 100644
index 0000000..27ccb2f
--- /dev/null
+++ b/proxy/http/ForwardedConfig.cc
@@ -0,0 +1,189 @@
+/** @file
+
+ Configuration of Forwarded HTTP header option.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#include <bitset>
+#include <string>
+#include <cctype>
+
+#include <ts/string_view.h>
+#include <ts/MemView.h>
+
+#include <HttpConfig.h>
+
+namespace
+{
+class BadOptionsErrMsg
+{
+public:
+ // Construct with referece to string that will contain error message.
+ //
+ BadOptionsErrMsg(ts::FixedBufferWriter &err) : _err(err), _count(0) {}
+
+ // Add a bad option.
+ //
+ void
+ add(ts::StringView badOpt)
+ {
+ if (_count == 0) {
+ _err << "\"Forwarded\" configuration: ";
+ _addQuoted(badOpt);
+ _count = 1;
+ } else if (_count == 1) {
+ _saveLast = badOpt;
+ _count = 2;
+ } else {
+ _err << ", ";
+ _addQuoted(_saveLast);
+ _saveLast = badOpt;
+ ++_count;
+ }
+ }
+
+ // Returns true it error seen.
+ //
+ bool
+ done()
+ {
+ if (_count == 0) {
+ return false;
+ }
+
+ if (_count == 1) {
+ _err << " is a bad option.";
+
+ } else if (_count != 0) {
+ _err << " and ";
+ _addQuoted(_saveLast);
+ _err << " are bad options.";
+ }
+ return true;
+ }
+
+private:
+ void
+ _addQuoted(ts::StringView sv)
+ {
+ _err << '\"' << ts::string_view(sv.begin(), sv.size()) << '\"';
+ }
+
+ ts::FixedBufferWriter &_err;
+
+ ts::StringView _saveLast;
+
+ int _count;
+};
+
+// Compare a StringView to a nul-termimated string, converting the StringView to lower case and ignoring whitespace in it.
+//
+bool
+eqIgnoreCaseWs(ts::StringView sv, const char *target)
+{
+ const char *s = sv.begin();
+
+ std::size_t skip = 0;
+ std::size_t i = 0;
+
+ while ((i + skip) < sv.size()) {
+ if (std::isspace(s[i + skip])) {
+ ++skip;
+ } else if (std::tolower(s[i + skip]) != target[i]) {
+ return false;
+ } else {
+ ++i;
+ }
+ }
+
+ return target[i] == '\0';
+}
+
+} // end anonymous namespace
+
+namespace HttpForwarded
+{
+OptionBitSet
+optStrToBitset(ts::string_view optConfigStr, ts::FixedBufferWriter &error)
+{
+ const ts::StringView Delimiters(":|");
+
+ OptionBitSet optBS;
+
+ // Convert to TS StringView to be able to use parsing members.
+ //
+ ts::StringView oCS(optConfigStr.data(), optConfigStr.size());
+
+ if (eqIgnoreCaseWs(oCS, "none")) {
+ return OptionBitSet();
+ }
+
+ BadOptionsErrMsg em(error);
+
+ do {
+ ts::StringView optStr = oCS.extractPrefix(Delimiters);
+
+ if (eqIgnoreCaseWs(optStr, "for")) {
+ optBS.set(FOR);
+
+ } else if (eqIgnoreCaseWs(optStr, "by=ip")) {
+ optBS.set(BY_IP);
+
+ } else if (eqIgnoreCaseWs(optStr, "by=unknown")) {
+ optBS.set(BY_UNKNOWN);
+
+ } else if (eqIgnoreCaseWs(optStr, "by=servername")) {
+ optBS.set(BY_SERVER_NAME);
+
+ } else if (eqIgnoreCaseWs(optStr, "by=uuid")) {
+ optBS.set(BY_UUID);
+
+ } else if (eqIgnoreCaseWs(optStr, "proto")) {
+ optBS.set(PROTO);
+
+ } else if (eqIgnoreCaseWs(optStr, "host")) {
+ optBS.set(HOST);
+
+ } else if (eqIgnoreCaseWs(optStr, "connection=compact")) {
+ optBS.set(CONNECTION_COMPACT);
+
+ } else if (eqIgnoreCaseWs(optStr, "connection=std")) {
+ optBS.set(CONNECTION_STD);
+
+ } else if (eqIgnoreCaseWs(optStr, "connection=standard")) {
+ optBS.set(CONNECTION_STD);
+
+ } else if (eqIgnoreCaseWs(optStr, "connection=full")) {
+ optBS.set(CONNECTION_FULL);
+
+ } else {
+ em.add(optStr);
+ }
+ } while (oCS);
+
+ if (em.done()) {
+ return OptionBitSet();
+ }
+
+ return optBS;
+
+} // end optStrToBitset()
+
+} // end namespace HttpForwarded
diff --git a/proxy/http/HttpConfig.cc b/proxy/http/HttpConfig.cc
index 169439d..1ab8796 100644
--- a/proxy/http/HttpConfig.cc
+++ b/proxy/http/HttpConfig.cc
@@ -179,6 +179,33 @@ http_server_session_sharing_cb(const char *name, RecDataT dtype, RecData data, v
return REC_ERR_OKAY;
}
+static int
+http_insert_forwarded_cb(const char *name, RecDataT dtype, RecData data, void *cookie)
+{
+ bool valid_p = false;
+ HttpConfigParams *c = static_cast<HttpConfigParams *>(cookie);
+
+ if (0 == strcasecmp("proxy.config.http.insert_forwarded", name)) {
+ if (RECD_STRING == dtype) {
+ ts::LocalBufferWriter<1024> error;
+ HttpForwarded::OptionBitSet bs = HttpForwarded::optStrToBitset(ts::string_view(data.rec_string), error);
+ if (!error.size()) {
+ c->oride.insert_forwarded = bs;
+ valid_p = true;
+ } else {
+ Error("HTTP %.*s", static_cast<int>(error.size()), error.data());
+ }
+ }
+ }
+
+ // Signal an update if valid value arrived.
+ if (valid_p) {
+ http_config_cb(name, dtype, data, cookie);
+ }
+
+ return REC_ERR_OKAY;
+}
+
void
register_stat_callbacks()
{
@@ -938,6 +965,21 @@ HttpConfig::startup()
c.oride.server_session_sharing_match);
http_config_enum_read("proxy.config.http.server_session_sharing.pool", SessionSharingPoolStrings, c.server_session_sharing_pool);
+ RecRegisterConfigUpdateCb("proxy.config.http.insert_forwarded", &http_insert_forwarded_cb, &c);
+ {
+ char str[512];
+
+ if (REC_ERR_OKAY == RecGetRecordString("proxy.config.http.insert_forwarded", str, sizeof(str))) {
+ ts::LocalBufferWriter<1024> error;
+ HttpForwarded::OptionBitSet bs = HttpForwarded::optStrToBitset(ts::string_view(str), error);
+ if (!error.size()) {
+ c.oride.insert_forwarded = bs;
+ } else {
+ Error("HTTP %.*s", static_cast<int>(error.size()), error.data());
+ }
+ }
+ }
+
HttpEstablishStaticConfigByte(c.oride.auth_server_session_private, "proxy.config.http.auth_server_session_private");
HttpEstablishStaticConfigByte(c.oride.keep_alive_post_out, "proxy.config.http.keep_alive_post_out");
@@ -1278,6 +1320,7 @@ HttpConfig::reconfigure()
params->oride.proxy_response_server_enabled = m_master.oride.proxy_response_server_enabled;
params->oride.insert_squid_x_forwarded_for = INT_TO_BOOL(m_master.oride.insert_squid_x_forwarded_for);
+ params->oride.insert_forwarded = m_master.oride.insert_forwarded;
params->oride.insert_age_in_response = INT_TO_BOOL(m_master.oride.insert_age_in_response);
params->enable_http_stats = INT_TO_BOOL(m_master.enable_http_stats);
params->oride.normalize_ae = m_master.oride.normalize_ae;
diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h
index bd4c4f7..b4e1d81 100644
--- a/proxy/http/HttpConfig.h
+++ b/proxy/http/HttpConfig.h
@@ -36,6 +36,7 @@
#include <stdlib.h>
#include <stdio.h>
+#include <bitset>
#ifdef HAVE_CTYPE_H
#include <ctype.h>
@@ -44,6 +45,8 @@
#include "ts/ink_platform.h"
#include "ts/ink_inet.h"
#include "ts/Regex.h"
+#include "ts/string_view.h"
+#include "ts/BufferWriter.h"
#include "HttpProxyAPIEnums.h"
#include "ProxyConfig.h"
#include "P_RecProcess.h"
@@ -358,6 +361,34 @@ struct HttpConfigPortRange {
}
};
+namespace HttpForwarded
+{
+// Options for what parameters will be included in "Forwarded" field header.
+//
+enum Option {
+ FOR,
+ BY_IP, // by=<numeric IP address>.
+ BY_UNKNOWN, // by=unknown.
+ BY_SERVER_NAME, // by=<configured server name>.
+ BY_UUID, // Obfuscated value for by, by=_<UUID>.
+ PROTO, // Basic protocol (http, https) of incoming message.
+ HOST, // Host from URL before any remapping.
+ CONNECTION_COMPACT, // Same value as 'proto' parameter.
+ CONNECTION_STD, // Verbose protocol from Via: field, with dashes instead of spaces.
+ CONNECTION_FULL, // Ultra-verbose protocol from Via: field, with dashes instead of spaces.
+
+ NUM_OPTIONS // Number of options.
+};
+
+using OptionBitSet = std::bitset<NUM_OPTIONS>;
+
+// Converts string specifier for Forwarded options to bitset of options, and return the result. If there are errors, an error
+// message will be inserted into 'error'.
+//
+OptionBitSet optStrToBitset(ts::string_view optConfigStr, ts::FixedBufferWriter &error);
+
+} // end HttpForwarded namespace
+
/////////////////////////////////////////////////////////////
// This is a little helper class, used by the HttpConfigParams
// and State (txn) structure. It allows for certain configs
@@ -388,6 +419,7 @@ struct OverridableHttpConfigParams {
proxy_response_server_enabled(1),
proxy_response_hsts_include_subdomains(0),
insert_squid_x_forwarded_for(1),
+ insert_forwarded(HttpForwarded::OptionBitSet()),
send_http11_requests(1),
cache_http(1),
cache_ignore_client_no_cache(1),
@@ -528,6 +560,11 @@ struct OverridableHttpConfigParams {
/////////////////////
MgmtByte insert_squid_x_forwarded_for;
+ ///////////////
+ // Forwarded //
+ ///////////////
+ HttpForwarded::OptionBitSet insert_forwarded;
+
//////////////////////
// Version Hell //
//////////////////////
diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc
index 51cdcd3..2c26aee 100644
--- a/proxy/http/HttpTransact.cc
+++ b/proxy/http/HttpTransact.cc
@@ -7536,6 +7536,7 @@ HttpTransact::build_request(State *s, HTTPHdr *base_request, HTTPHdr *outgoing_r
HttpTransactHeaders::copy_header_fields(base_request, outgoing_request, s->txn_conf->fwd_proxy_auth_to_parent);
add_client_ip_to_outgoing_request(s, outgoing_request);
+ HttpTransactHeaders::add_forwarded_field_to_request(s, outgoing_request);
HttpTransactHeaders::remove_privacy_headers_from_request(s->http_config_param, s->txn_conf, outgoing_request);
HttpTransactHeaders::add_global_user_agent_header_to_request(s->txn_conf, outgoing_request);
handle_request_keep_alive_headers(s, outgoing_version, outgoing_request);
diff --git a/proxy/http/HttpTransactHeaders.cc b/proxy/http/HttpTransactHeaders.cc
index a8cb790..358dd45 100644
--- a/proxy/http/HttpTransactHeaders.cc
+++ b/proxy/http/HttpTransactHeaders.cc
@@ -20,7 +20,12 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
-#include "ts/ink_platform.h"
+
+#include <bitset>
+#include <algorithm>
+
+#include <ts/ink_platform.h>
+#include <ts/BufferWriter.h>
#include "HttpTransact.h"
#include "HttpTransactHeaders.h"
@@ -686,50 +691,56 @@ HttpTransactHeaders::insert_server_header_in_response(const char *server_tag, in
/// write the protocol stack to the @a via_string.
/// If @a detailed then do the full stack, otherwise just the "top level" protocol.
-size_t
-write_via_protocol_stack(char *via_string, size_t len, bool detailed, ts::StringView *proto_buf, int n_proto)
+/// Returns the number of characters appended to hdr_string (no nul appended).
+int
+HttpTransactHeaders::write_hdr_protocol_stack(char *hdr_string, size_t len, ProtocolStackDetail pSDetail, ts::StringView *proto_buf,
+ int n_proto, char separator)
{
- char *via = via_string; // keep original pointer for size computation later.
- char *limit = via_string + len;
+ char *hdr = hdr_string; // keep original pointer for size computation later.
+ char *limit = hdr_string + len;
static constexpr ts::StringView tls_prefix{"tls/", ts::StringView::literal};
- if (n_proto <= 0 || via == nullptr || len <= 0) {
+ if (n_proto <= 0 || hdr == nullptr || len <= 0) {
// nothing
- } else if (detailed) {
- for (ts::StringView *v = proto_buf, *v_limit = proto_buf + n_proto; v < v_limit && (via + v->size() + 1) < limit; ++v) {
+ } else if (ProtocolStackDetail::Full == pSDetail) {
+ for (ts::StringView *v = proto_buf, *v_limit = proto_buf + n_proto; v < v_limit && (hdr + v->size() + 1) < limit; ++v) {
if (v != proto_buf) {
- *via++ = ' ';
+ *hdr++ = separator;
}
- memcpy(via, v->ptr(), v->size());
- via += v->size();
+ memcpy(hdr, v->ptr(), v->size());
+ hdr += v->size();
}
} else {
ts::StringView *proto_end = proto_buf + n_proto;
bool http_1_0_p = std::find(proto_buf, proto_end, IP_PROTO_TAG_HTTP_1_0) != proto_end;
bool http_1_1_p = std::find(proto_buf, proto_end, IP_PROTO_TAG_HTTP_1_1) != proto_end;
- if ((http_1_0_p || http_1_1_p) && via + 10 < limit) {
+ if ((http_1_0_p || http_1_1_p) && hdr + 10 < limit) {
bool tls_p = std::find_if(proto_buf, proto_end, [](ts::StringView tag) { return tls_prefix.isPrefixOf(tag); }) != proto_end;
- bool http_2_p = std::find(proto_buf, proto_end, IP_PROTO_TAG_HTTP_2_0) != proto_end;
- memcpy(via, "http", 4);
- via += 4;
+ memcpy(hdr, "http", 4);
+ hdr += 4;
if (tls_p)
- *via++ = 's';
- *via++ = '/';
- if (http_2_p) {
- *via++ = '2';
- } else if (http_1_0_p) {
- memcpy(via, "1.0", 3);
- via += 3;
- } else if (http_1_1_p) {
- memcpy(via, "1.1", 3);
- via += 3;
+ *hdr++ = 's';
+
+ // If detail level is compact (RFC 7239 compliant "proto" value for Forwarded field), stop here.
+
+ if (ProtocolStackDetail::Standard == pSDetail) {
+ *hdr++ = '/';
+ bool http_2_p = std::find(proto_buf, proto_end, IP_PROTO_TAG_HTTP_2_0) != proto_end;
+ if (http_2_p) {
+ *hdr++ = '2';
+ } else if (http_1_0_p) {
+ memcpy(hdr, "1.0", 3);
+ hdr += 3;
+ } else if (http_1_1_p) {
+ memcpy(hdr, "1.1", 3);
+ hdr += 3;
+ }
}
- *via++ = ' ';
}
}
- return via - via_string;
+ return hdr - hdr_string;
}
///////////////////////////////////////////////////////////////////////////////
@@ -792,7 +803,10 @@ HttpTransactHeaders::insert_via_header_in_request(HttpTransact::State *s, HTTPHd
std::array<ts::StringView, 10> proto_buf; // 10 seems like a reasonable number of protos to print
int n_proto = s->state_machine->populate_client_protocol(proto_buf.data(), proto_buf.size());
- via_string += write_via_protocol_stack(via_string, via_limit - via_string, false, proto_buf.data(), n_proto);
+ via_string +=
+ write_hdr_protocol_stack(via_string, via_limit - via_string, ProtocolStackDetail::Standard, proto_buf.data(), n_proto);
+ *via_string++ = ' ';
+
via_string += nstrcpy(via_string, s->http_config_param->proxy_hostname);
*via_string++ = '[';
@@ -822,7 +836,8 @@ HttpTransactHeaders::insert_via_header_in_request(HttpTransact::State *s, HTTPHd
if (via_limit - via_string > 4 && s->txn_conf->insert_request_via_string > 3) { // Ultra highest verbosity
*via_string++ = ' ';
*via_string++ = '[';
- via_string += write_via_protocol_stack(via_string, via_limit - via_string - 3, true, proto_buf.data(), n_proto);
+ via_string +=
+ write_hdr_protocol_stack(via_string, via_limit - via_string - 3, ProtocolStackDetail::Full, proto_buf.data(), n_proto);
*via_string++ = ']';
}
}
@@ -877,7 +892,9 @@ HttpTransactHeaders::insert_via_header_in_response(HttpTransact::State *s, HTTPH
if (ss) {
n_proto += ss->populate_protocol(proto_buf.data() + n_proto, proto_buf.size() - n_proto);
}
- via_string += write_via_protocol_stack(via_string, via_limit - via_string, false, proto_buf.data(), n_proto);
+ via_string +=
+ write_hdr_protocol_stack(via_string, via_limit - via_string, ProtocolStackDetail::Standard, proto_buf.data(), n_proto);
+ *via_string++ = ' ';
via_string += nstrcpy(via_string, s->http_config_param->proxy_hostname);
*via_string++ = ' ';
@@ -902,7 +919,8 @@ HttpTransactHeaders::insert_via_header_in_response(HttpTransact::State *s, HTTPH
if (via_limit - via_string > 4 && s->txn_conf->insert_response_via_string > 3) { // Ultra highest verbosity
*via_string++ = ' ';
*via_string++ = '[';
- via_string += write_via_protocol_stack(via_string, via_limit - via_string - 3, true, proto_buf.data(), n_proto);
+ via_string +=
+ write_hdr_protocol_stack(via_string, via_limit - via_string - 3, ProtocolStackDetail::Full, proto_buf.data(), n_proto);
*via_string++ = ']';
}
}
@@ -996,6 +1014,191 @@ HttpTransactHeaders::add_global_user_agent_header_to_request(OverridableHttpConf
}
void
+HttpTransactHeaders::add_forwarded_field_to_request(HttpTransact::State *s, HTTPHdr *request)
+{
+ HttpForwarded::OptionBitSet optSet = s->txn_conf->insert_forwarded;
+
+ if (optSet.any()) { // One or more Forwarded parameters enabled, so insert/append to Forwarded header.
+
+ ts::LocalBufferWriter<1024> hdr;
+
+ if (optSet[HttpForwarded::FOR] and ats_is_ip(&s->client_info.src_addr.sa)) {
+ // NOTE: The logic within this if statement assumes that hdr is empty at this point.
+
+ hdr << "for=";
+
+ bool is_ipv6 = ats_is_ip6(&s->client_info.src_addr.sa);
+
+ if (is_ipv6) {
+ hdr << "\"[";
+ }
+
+ if (ats_ip_ntop(&s->client_info.src_addr.sa, hdr.auxBuffer(), hdr.remaining()) == nullptr) {
+ Debug("http_trans", "[add_forwarded_field_to_outgoing_request] ats_ip_ntop() call failed");
+ return;
+ }
+
+ // Fail-safe.
+ hdr.auxBuffer()[hdr.remaining() - 1] = '\0';
+
+ hdr.write(strlen(hdr.auxBuffer()));
+
+ if (is_ipv6) {
+ hdr << "]\"";
+ }
+ }
+
+ if (optSet[HttpForwarded::BY_UNKNOWN]) {
+ if (hdr.size()) {
+ hdr << ';';
+ }
+
+ hdr << "by=unknown";
+ }
+
+ if (optSet[HttpForwarded::BY_SERVER_NAME]) {
+ if (hdr.size()) {
+ hdr << ';';
+ }
+
+ hdr << "by=" << s->http_config_param->proxy_hostname;
+ }
+
+ const Machine &m = *Machine::instance();
+
+ if (optSet[HttpForwarded::BY_UUID] and m.uuid.valid()) {
+ if (hdr.size()) {
+ hdr << ';';
+ }
+
+ hdr << "by=_" << m.uuid.getString();
+ }
+
+ if (optSet[HttpForwarded::BY_IP] and (m.ip_string_len > 0)) {
+ if (hdr.size()) {
+ hdr << ';';
+ }
+
+ hdr << "by=";
+
+ bool is_ipv6 = ats_is_ip6(&s->client_info.dst_addr.sa);
+
+ if (is_ipv6) {
+ hdr << "\"[";
+ }
+
+ if (ats_ip_ntop(&s->client_info.dst_addr.sa, hdr.auxBuffer(), hdr.remaining()) == nullptr) {
+ Debug("http_trans", "[add_forwarded_field_to_outgoing_request] ats_ip_ntop() call failed");
+ return;
+ }
+
+ // Fail-safe.
+ hdr.auxBuffer()[hdr.remaining() - 1] = '\0';
+
+ hdr.write(strlen(hdr.auxBuffer()));
+
+ if (is_ipv6) {
+ hdr << "]\"";
+ }
+ }
+
+ std::array<ts::StringView, 10> protoBuf; // 10 seems like a reasonable number of protos to print
+ int nProto = 0; // Indulge clang's incorrect claim that this need to be initialized.
+
+ static const HttpForwarded::OptionBitSet OptionsNeedingProtocol = HttpForwarded::OptionBitSet()
+ .set(HttpForwarded::PROTO)
+ .set(HttpForwarded::CONNECTION_COMPACT)
+ .set(HttpForwarded::CONNECTION_STD)
+ .set(HttpForwarded::CONNECTION_FULL);
+
+ if ((optSet bitand OptionsNeedingProtocol).any()) {
+ nProto = s->state_machine->populate_client_protocol(protoBuf.data(), protoBuf.size());
+ }
+
+ if (optSet[HttpForwarded::PROTO] and (nProto > 0)) {
+ if (hdr.size()) {
+ hdr << ';';
+ }
+
+ hdr << "proto=";
+
+ int numChars = HttpTransactHeaders::write_hdr_protocol_stack(
+ hdr.auxBuffer(), hdr.remaining(), HttpTransactHeaders::ProtocolStackDetail::Compact, protoBuf.data(), nProto, '-');
+ if (numChars > 0) {
+ hdr.write(size_t(numChars));
+ }
+ }
+
+ if (optSet[HttpForwarded::HOST]) {
+ const MIMEField *hostField = s->hdr_info.client_request.field_find(MIME_FIELD_HOST, MIME_LEN_HOST);
+
+ if (hostField and hostField->m_len_value) {
+ ts::string_view hSV{hostField->m_ptr_value, hostField->m_len_value};
+
+ bool needsDoubleQuotes = hSV.find(':') != ts::string_view::npos;
+
+ if (hdr.size()) {
+ hdr << ';';
+ }
+
+ hdr << "host=";
+ if (needsDoubleQuotes) {
+ hdr << '"';
+ }
+ hdr << hSV;
+ if (needsDoubleQuotes) {
+ hdr << '"';
+ }
+ }
+ }
+
+ if (nProto > 0) {
+ auto Conn = [&](HttpForwarded::Option opt, HttpTransactHeaders::ProtocolStackDetail detail) -> void {
+ if (optSet[opt]) {
+ int revert = hdr.size();
+
+ if (hdr.size()) {
+ hdr << ';';
+ }
+
+ hdr << "connection=";
+
+ int numChars =
+ HttpTransactHeaders::write_hdr_protocol_stack(hdr.auxBuffer(), hdr.remaining(), detail, protoBuf.data(), nProto, '-');
+ if (numChars > 0) {
+ hdr.write(size_t(numChars));
+ }
+
+ if ((numChars <= 0) or (hdr.size() >= hdr.capacity())) {
+ // Remove parameter with potentially incomplete value.
+ //
+ hdr.reduce(revert);
+ }
+ }
+ };
+
+ Conn(HttpForwarded::CONNECTION_COMPACT, HttpTransactHeaders::ProtocolStackDetail::Compact);
+ Conn(HttpForwarded::CONNECTION_STD, HttpTransactHeaders::ProtocolStackDetail::Standard);
+ Conn(HttpForwarded::CONNECTION_FULL, HttpTransactHeaders::ProtocolStackDetail::Full);
+ }
+
+ // Add or append to the Forwarded header. As a fail-safe against corrupting the MIME header, don't add Forwarded if
+ // it's size is exactly the capacity of the buffer.
+ //
+ if (hdr.size() and !hdr.error() and (hdr.size() < hdr.capacity())) {
+ ts::string_view sV = hdr.view();
+
+ request->value_append(MIME_FIELD_FORWARDED, MIME_LEN_FORWARDED, sV.data(), sV.size(), true, ','); // true => separator must
+ // be inserted
+
+ Debug("http_trans", "[add_forwarded_field_to_outgoing_request] Forwarded header (%.*s) added", static_cast<int>(hdr.size()),
+ hdr.data());
+ }
+ }
+
+} // end HttpTransact::add_forwarded_field_to_outgoing_request()
+
+void
HttpTransactHeaders::add_server_header_to_response(OverridableHttpConfigParams *http_txn_conf, HTTPHdr *header)
{
if (http_txn_conf->proxy_response_server_enabled && http_txn_conf->proxy_response_server_string) {
diff --git a/proxy/http/HttpTransactHeaders.h b/proxy/http/HttpTransactHeaders.h
index f4b243d..57d468f 100644
--- a/proxy/http/HttpTransactHeaders.h
+++ b/proxy/http/HttpTransactHeaders.h
@@ -62,6 +62,11 @@ public:
static void generate_and_set_squid_codes(HTTPHdr *header, char *via_string, HttpTransact::SquidLogInfo *squid_codes);
+ enum class ProtocolStackDetail { Compact, Standard, Full };
+
+ static int write_hdr_protocol_stack(char *hdr_string, size_t len, ProtocolStackDetail pSDetail, ts::StringView *proto_buf,
+ int n_proto, char separator = ' ');
+
// Removing handle_conditional_headers. Functionality appears to be elsewhere (issue_revalidate)
// and the only condition when it does anything causes an assert to go
// off
@@ -75,6 +80,8 @@ public:
static void insert_via_header_in_response(HttpTransact::State *s, HTTPHdr *header);
static void insert_hsts_header_in_response(HttpTransact::State *s, HTTPHdr *header);
+ static void add_forwarded_field_to_request(HttpTransact::State *s, HTTPHdr *request);
+
static bool is_request_proxy_authorized(HTTPHdr *incoming_hdr);
static void normalize_accept_encoding(const OverridableHttpConfigParams *ohcp, HTTPHdr *header);
diff --git a/proxy/http/Makefile.am b/proxy/http/Makefile.am
index fc8ee91..a08737e 100644
--- a/proxy/http/Makefile.am
+++ b/proxy/http/Makefile.am
@@ -72,13 +72,28 @@ libhttp_a_SOURCES = \
HttpTunnel.cc \
HttpTunnel.h \
HttpUpdateSM.cc \
- HttpUpdateSM.h
+ HttpUpdateSM.h \
+ ForwardedConfig.cc
if BUILD_TESTS
libhttp_a_SOURCES += HttpUpdateTester.cc \
RegressionHttpTransact.cc
endif
+check_PROGRAMS = \
+test_ForwardedConfig
+
+TESTS = $(check_PROGRAMS)
+
+test_ForwardedConfig_CPPFLAGS = $(AM_CPPFLAGS)\
+ -I$(abs_top_srcdir)/tests/include
+
+test_ForwardedConfig_SOURCES = \
+ unit-tests/test_ForwardedConfig.cc \
+ ForwardedConfig.cc \
+ unit-tests/test_ForwardedConfig_mocks.cc \
+ unit-tests/sym-links/MemView.cc
+
tidy-local: $(libhttp_a_SOURCES) $(noinst_HEADERS)
$(CXX_Clang_Tidy)
diff --git a/proxy/http/unit-tests/sym-links/MemView.cc b/proxy/http/unit-tests/sym-links/MemView.cc
new file mode 120000
index 0000000..51e80fb
--- /dev/null
+++ b/proxy/http/unit-tests/sym-links/MemView.cc
@@ -0,0 +1 @@
+../../../../lib/ts/MemView.cc
\ No newline at end of file
diff --git a/proxy/http/unit-tests/test_ForwardedConfig.cc b/proxy/http/unit-tests/test_ForwardedConfig.cc
new file mode 100644
index 0000000..712eb9b
--- /dev/null
+++ b/proxy/http/unit-tests/test_ForwardedConfig.cc
@@ -0,0 +1,169 @@
+/** @file
+
+ Catch-based tests for ForwardedConfig.cc.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#include <string>
+#include <cstring>
+#include <cctype>
+#include <bitset>
+#include <initializer_list>
+
+#define CATCH_CONFIG_MAIN
+#include "catch.hpp"
+
+#include "HttpConfig.h"
+
+using namespace HttpForwarded;
+
+class OptionBitSetListInit : public OptionBitSet
+{
+public:
+ OptionBitSetListInit(std::initializer_list<std::size_t> il)
+ {
+ for (std::size_t i : il) {
+ this->set(i);
+ }
+ }
+};
+
+namespace
+{
+const char *wsTbl[] = {"", " ", " ", nullptr};
+
+int wsIdx{0};
+
+const char *
+nextWs()
+{
+ ++wsIdx;
+
+ if (!wsTbl[wsIdx]) {
+ wsIdx = 0;
+ }
+
+ return wsTbl[wsIdx];
+}
+// Alternate upper/lower case and add blanks.
+class XS
+{
+private:
+ std::string s;
+
+public:
+ XS(const char *in) : s{nextWs()}
+ {
+ bool upper{true};
+ for (; *in; ++in) {
+ if (islower(*in)) {
+ s += upper ? toupper(*in) : *in;
+ upper = !upper;
+
+ } else if (isupper(*in)) {
+ s += upper ? *in : tolower(*in);
+ upper = !upper;
+
+ } else {
+ s += *in;
+ }
+ s += nextWs();
+ }
+ s += nextWs();
+ }
+
+ operator ts::string_view() const { return ts::string_view(s.c_str()); }
+};
+
+void
+test(const char *spec, const char *reqErr, OptionBitSet bS)
+{
+ ts::LocalBufferWriter<1024> error;
+
+ error << "cheese";
+
+ REQUIRE(bS == optStrToBitset(XS(spec), error));
+ std::size_t len = std::strlen(reqErr);
+ REQUIRE((error.size() - sizeof("cheese") + 1) == len);
+ REQUIRE(std::memcmp(error.data() + sizeof("cheese") - 1, reqErr, len) == 0);
+}
+
+} // end annonymous namespace
+
+TEST_CASE("Forwarded", "[FWD]")
+{
+ test("none", "", OptionBitSet());
+
+ test("", "\"Forwarded\" configuration: \" \" is a bad option.", OptionBitSet());
+
+ test("\t", "\"Forwarded\" configuration: \"\t \" is a bad option.", OptionBitSet());
+
+ test(":", "\"Forwarded\" configuration: \" \" is a bad option.", OptionBitSet());
+
+ test("|", "\"Forwarded\" configuration: \" \" is a bad option.", OptionBitSet());
+
+ test("by=ip", "", OptionBitSetListInit{BY_IP});
+
+ test("by=unknown", "", OptionBitSetListInit{BY_UNKNOWN});
+
+ test("by=servername", "", OptionBitSetListInit{BY_SERVER_NAME});
+
+ test("by=uuid", "", OptionBitSetListInit{BY_UUID});
+
+ test("for", "", OptionBitSetListInit{FOR});
+
+ test("proto", "", OptionBitSetListInit{PROTO});
+
+ test("host", "", OptionBitSetListInit{HOST});
+
+ test("connection=compact", "", OptionBitSetListInit{CONNECTION_COMPACT});
+
+ test("connection=standard", "", OptionBitSetListInit{CONNECTION_STD});
+
+ test("connection=std", "", OptionBitSetListInit{CONNECTION_STD});
+
+ test("connection=full", "", OptionBitSetListInit{CONNECTION_FULL});
+
+ test("proto:by=uuid|for", "", OptionBitSetListInit{PROTO, BY_UUID, FOR});
+
+ test("proto:by=cheese|fur", "\"Forwarded\" configuration: \" b Y= c He E sE \" and \" fU r \" are bad options.",
+ OptionBitSet());
+
+ test("proto:by=cheese|fur|compact=",
+ "\"Forwarded\" configuration: \" b Y= c He E sE \", \" fU r \" and \"C o Mp A cT = \" are bad options.",
+ OptionBitSet());
+
+#undef X
+#define X(S) \
+ "by=ip" S "by=unknown" S "by=servername" S "by=uuid" S "for" S "proto" S "host" S "connection=compact" S "connection=std" S \
+ "connection=full"
+
+ test(X(":"), "", OptionBitSet().set());
+
+ test(X("|"), "", OptionBitSet().set());
+
+ test(X("|") "|" X(":"), "", OptionBitSet().set());
+
+ test(X("|") ":abcd", "\"Forwarded\" configuration: \" aB c D \" is a bad option.", OptionBitSet());
+
+ test(X("|") ":for=abcd", "\"Forwarded\" configuration: \" f Or = Ab C d \" is a bad option.", OptionBitSet());
+
+ test(X("|") ":by", "\"Forwarded\" configuration: \" b Y \" is a bad option.", OptionBitSet());
+}
diff --git a/proxy/http/unit-tests/test_ForwardedConfig_mocks.cc b/proxy/http/unit-tests/test_ForwardedConfig_mocks.cc
new file mode 100644
index 0000000..a0fe062
--- /dev/null
+++ b/proxy/http/unit-tests/test_ForwardedConfig_mocks.cc
@@ -0,0 +1,86 @@
+/** @file
+
+ Mocks for unit test of ForwardedConfig.cc
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#include <cstdlib>
+#include <iostream>
+
+#include <I_EventSystem.h>
+#include <I_Thread.h>
+
+void
+_ink_assert(const char *expression, const char *file, int line)
+{
+ std::cerr << "fatal error: ink_assert: file: " << file << " line: " << line << " expression: " << expression << std::endl;
+
+ std::exit(1);
+}
+
+namespace
+{
+void
+stub(const char *file, int line)
+{
+ std::cerr << "fatal error: call to link stub: file: " << file << " line: " << line << std::endl;
+
+ std::exit(1);
+}
+}
+
+#define STUB stub(__FILE__, __LINE__);
+
+inkcoreapi void
+ink_freelist_init(InkFreeList **fl, const char *name, uint32_t type_size, uint32_t chunk_size, uint32_t alignment)
+{
+}
+inkcoreapi void
+ink_freelist_free(InkFreeList *f, void *item){STUB} inkcoreapi
+ void ink_freelist_free_bulk(InkFreeList *f, void *head, void *tail, size_t num_item)
+{
+ STUB
+}
+void ink_mutex_destroy(pthread_mutex_t *){STUB} inkcoreapi ClassAllocator<ProxyMutex> mutexAllocator("ARGH");
+inkcoreapi ink_thread_key Thread::thread_data_key;
+volatile int res_track_memory;
+void ResourceTracker::increment(const char *, long){STUB} inkcoreapi Allocator ioBufAllocator[DEFAULT_BUFFER_SIZES];
+void
+ats_free(void *)
+{
+ STUB
+}
+int thread_freelist_high_watermark;
+int thread_freelist_low_watermark;
+inkcoreapi ClassAllocator<IOBufferBlock> ioBlockAllocator("ARGH");
+inkcoreapi ClassAllocator<IOBufferData> ioDataAllocator("ARGH");
+IOBufferBlock::IOBufferBlock()
+{
+}
+
+void
+IOBufferBlock::free()
+{
+}
+
+void
+IOBufferData::free()
+{
+}
diff --git a/tests/gold_tests/headers/forwarded-observer.py b/tests/gold_tests/headers/forwarded-observer.py
new file mode 100644
index 0000000..91b7baf
--- /dev/null
+++ b/tests/gold_tests/headers/forwarded-observer.py
@@ -0,0 +1,63 @@
+'''
+Extract the protocol information from the FORWARDED headers and store it in a log file for later verification.
+'''
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import re
+import subprocess
+
+log = open('forwarded.log', 'w')
+
+regexByEqualUuid = re.compile("^by=_[0-9a-f-]+$")
+
+byCount = 0;
+byEqualUuid = "__INVALID__"
+
+def observe(headers):
+
+ global byCount
+ global byEqualUuid
+
+ seen = False
+ for h in headers.items():
+ if h[0].lower() == "forwarded":
+
+ content = h[1]
+
+ if content.startswith("by="):
+
+ byCount += 1
+
+ if ((byCount == 4) or (byCount == 5)) and regexByEqualUuid.match(content): # "by" should give UUID
+
+ # I don't think there is a way to know what UUID traffic_server generates, so I just do a crude format
+ # check and make sure the same value is used consistently.
+
+ byEqualUuid = content
+
+ content = content.replace(byEqualUuid, "__BY_EQUAL_UUID__", 1)
+
+ log.write(content + "\n")
+ seen = True
+
+ if not seen:
+ log.write("FORWARDED MISSING\n")
+ log.write("-\n")
+ log.flush()
+
+
+Hooks.register(Hooks.ReadRequestHook, observe)
diff --git a/tests/gold_tests/headers/forwarded.gold b/tests/gold_tests/headers/forwarded.gold
new file mode 100644
index 0000000..45451d6
--- /dev/null
+++ b/tests/gold_tests/headers/forwarded.gold
@@ -0,0 +1,41 @@
+FORWARDED MISSING
+-
+FORWARDED MISSING
+-
+for=127.0.0.1
+-
+by=127.0.0.1
+-
+by=unknown
+-
+by=Poxy_Proxy
+-
+__BY_EQUAL_UUID__
+-
+proto=http
+-
+host=www.forwarded-host.com
+-
+connection=http
+-
+connection=http/1.1
+-
+connection=http/1.1-tcp-ipv4
+-
+__BY_EQUAL_UUID__
+-
+for=127.0.0.1;by=unknown;by=Poxy_Proxy;__BY_EQUAL_UUID__;by=127.0.0.1;proto=http;host=www.no-oride.com;connection=http;connection=http/1.1;connection=http/1.1-tcp-ipv4
+-
+for=127.0.0.1;by=unknown;by=Poxy_Proxy;__BY_EQUAL_UUID__;by=127.0.0.1;proto=http;host=www.no-oride.com;connection=http;connection=http/1.0;connection=http/1.0-tcp-ipv4
+-
+for=0.6.6.6
+for=_argh, for=127.0.0.1;by=unknown;by=Poxy_Proxy;__BY_EQUAL_UUID__;by=127.0.0.1;proto=http;host=www.no-oride.com;connection=http;connection=http/1.0;connection=http/1.0-tcp-ipv4
+-
+for=127.0.0.1;by=unknown;by=Poxy_Proxy;__BY_EQUAL_UUID__;by=127.0.0.1;proto=https;host=www.no-oride.com;connection=https;connection=https/2;connection=http/1.1-h2-tls/1.2-tcp-ipv4
+-
+for=127.0.0.1;by=unknown;by=Poxy_Proxy;__BY_EQUAL_UUID__;by=127.0.0.1;proto=https;host=www.no-oride.com;connection=https;connection=https/1.1;connection=http/1.1-tls/1.2-tcp-ipv4
+-
+for="[::1]";by=unknown;by=Poxy_Proxy;__BY_EQUAL_UUID__;by="[::1]";proto=http;host=www.no-oride.com;connection=http;connection=http/1.1;connection=http/1.1-tcp-ipv6
+-
+for="[::1]";by=unknown;by=Poxy_Proxy;__BY_EQUAL_UUID__;by="[::1]";proto=https;host=www.no-oride.com;connection=https;connection=https/1.1;connection=http/1.1-tls/1.2-tcp-ipv6
+-
diff --git a/tests/gold_tests/headers/forwarded.test.py b/tests/gold_tests/headers/forwarded.test.py
new file mode 100644
index 0000000..e45a9d4
--- /dev/null
+++ b/tests/gold_tests/headers/forwarded.test.py
@@ -0,0 +1,289 @@
+'''
+Test the Forwarded header and related configuration..
+'''
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import subprocess
+
+Test.Summary = '''
+Test FORWARDED header.
+'''
+
+Test.SkipUnless(
+ Condition.HasATSFeature('TS_USE_TLS_ALPN'),
+ Condition.HasCurlFeature('http2'),
+ Condition.HasCurlFeature('IPv6')
+)
+Test.ContinueOnFail = True
+
+testName = "FORWARDED"
+
+server = Test.MakeOriginServer("server", options={'--load': os.path.join(Test.TestDirectory, 'forwarded-observer.py')})
+
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.no-oride.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.forwarded-none.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.forwarded-for.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.forwarded-by-ip.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.forwarded-by-unknown.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.forwarded-by-server-name.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.forwarded-by-uuid.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.forwarded-proto.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.forwarded-host.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.forwarded-connection-compact.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.forwarded-connection-std.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+request_header = {
+ "headers": "GET / HTTP/1.1\r\nHost: www.forwarded-connection-full.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+
+# Set up to check the output after the tests have run.
+#
+forwarded_log_id = Test.Disk.File("forwarded.log")
+forwarded_log_id.Content = "forwarded.gold"
+
+def baselineTsSetup(ts, sslPort):
+
+ ts.addSSLfile("../remap/ssl/server.pem")
+ ts.addSSLfile("../remap/ssl/server.key")
+
+ ts.Variables.ssl_port = sslPort
+
+ ts.Disk.records_config.update({
+ # 'proxy.config.diags.debug.enabled': 1,
+ 'proxy.config.url_remap.pristine_host_hdr': 1, # Retain Host header in original incoming client request.
+ 'proxy.config.http.cache.http': 0, # Make sure each request is forwarded to the origin server.
+ 'proxy.config.proxy_name': 'Poxy_Proxy', # This will be the server name.
+ 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
+ 'proxy.config.http.server_ports': (
+ 'ipv4:{0} ipv4:{1}:proto=http2;http:ssl ipv6:{0} ipv6:{1}:proto=http2;http:ssl'
+ .format(ts.Variables.port, ts.Variables.ssl_port))
+ })
+
+ ts.Disk.ssl_multicert_config.AddLine(
+ 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+ )
+
+ ts.Disk.remap_config.AddLine(
+ 'map http://www.no-oride.com http://127.0.0.1:{0}'.format(server.Variables.Port)
+ )
+
+ts = Test.MakeATSProcess("ts", select_ports=False)
+
+baselineTsSetup(ts, 4443)
+
+ts.Disk.remap_config.AddLine(
+ 'map http://www.forwarded-none.com http://127.0.0.1:{0}'.format(server.Variables.Port) +
+ ' @plugin=conf_remap.so @pparam=proxy.config.http.insert_forwarded=none'
+)
+ts.Disk.remap_config.AddLine(
+ 'map http://www.forwarded-for.com http://127.0.0.1:{0}'.format(server.Variables.Port) +
+ ' @plugin=conf_remap.so @pparam=proxy.config.http.insert_forwarded=for'
+)
+ts.Disk.remap_config.AddLine(
+ 'map http://www.forwarded-by-ip.com http://127.0.0.1:{0}'.format(server.Variables.Port) +
+ ' @plugin=conf_remap.so @pparam=proxy.config.http.insert_forwarded=by=ip'
+)
+ts.Disk.remap_config.AddLine(
+ 'map http://www.forwarded-by-unknown.com http://127.0.0.1:{0}'.format(server.Variables.Port) +
+ ' @plugin=conf_remap.so @pparam=proxy.config.http.insert_forwarded=by=unknown'
+)
+ts.Disk.remap_config.AddLine(
+ 'map http://www.forwarded-by-server-name.com http://127.0.0.1:{0}'.format(server.Variables.Port) +
+ ' @plugin=conf_remap.so @pparam=proxy.config.http.insert_forwarded=by=serverName'
+)
+ts.Disk.remap_config.AddLine(
+ 'map http://www.forwarded-by-uuid.com http://127.0.0.1:{0}'.format(server.Variables.Port) +
+ ' @plugin=conf_remap.so @pparam=proxy.config.http.insert_forwarded=by=uuid'
+)
+ts.Disk.remap_config.AddLine(
+ 'map http://www.forwarded-proto.com http://127.0.0.1:{0}'.format(server.Variables.Port) +
+ ' @plugin=conf_remap.so @pparam=proxy.config.http.insert_forwarded=proto'
+)
+ts.Disk.remap_config.AddLine(
+ 'map http://www.forwarded-host.com http://127.0.0.1:{0}'.format(server.Variables.Port) +
+ ' @plugin=conf_remap.so @pparam=proxy.config.http.insert_forwarded=host'
+)
+ts.Disk.remap_config.AddLine(
+ 'map http://www.forwarded-connection-compact.com http://127.0.0.1:{0}'.format(server.Variables.Port) +
+ ' @plugin=conf_remap.so @pparam=proxy.config.http.insert_forwarded=connection=compact'
+)
+ts.Disk.remap_config.AddLine(
+ 'map http://www.forwarded-connection-std.com http://127.0.0.1:{0}'.format(server.Variables.Port) +
+ ' @plugin=conf_remap.so @pparam=proxy.config.http.insert_forwarded=connection=std'
+)
+ts.Disk.remap_config.AddLine(
+ 'map http://www.forwarded-connection-full.com http://127.0.0.1:{0}'.format(server.Variables.Port) +
+ ' @plugin=conf_remap.so @pparam=proxy.config.http.insert_forwarded=connection=full'
+)
+
+# Ask the OS if the port is ready for connect()
+#
+def CheckPort(Port):
+ return lambda: 0 == subprocess.call('netstat --listen --tcp -n | grep -q :{}'.format(Port), shell=True)
+
+# Basic HTTP 1.1 -- No Forwarded by default
+tr = Test.AddTestRun()
+# Wait for the micro server
+tr.Processes.Default.StartBefore(server, ready=CheckPort(server.Variables.Port))
+# Delay on readiness of our ssl ports
+tr.Processes.Default.StartBefore(Test.Processes.ts, ready=CheckPort(ts.Variables.ssl_port))
+#
+tr.Processes.Default.Command = (
+ 'curl --verbose --ipv4 --http1.1 --proxy localhost:{} http://www.no-oride.com'.format(ts.Variables.port)
+)
+tr.Processes.Default.ReturnCode = 0
+
+def TestHttp1_1(host):
+
+ tr = Test.AddTestRun()
+ tr.Processes.Default.Command = (
+ 'curl --verbose --ipv4 --http1.1 --proxy localhost:{} http://{}'.format(ts.Variables.port, host)
+ )
+ tr.Processes.Default.ReturnCode = 0
+
+# Basic HTTP 1.1 -- No Forwarded -- explicit configuration.
+#
+TestHttp1_1('www.forwarded-none.com')
+
+# Test enabling of each forwarded parameter singly.
+
+TestHttp1_1('www.forwarded-for.com')
+
+# Note: forwaded-obsersver.py counts on the "by" tests being done in the order below.
+
+TestHttp1_1('www.forwarded-by-ip.com')
+TestHttp1_1('www.forwarded-by-unknown.com')
+TestHttp1_1('www.forwarded-by-server-name.com')
+TestHttp1_1('www.forwarded-by-uuid.com')
+
+TestHttp1_1('www.forwarded-proto.com')
+TestHttp1_1('www.forwarded-host.com')
+TestHttp1_1('www.forwarded-connection-compact.com')
+TestHttp1_1('www.forwarded-connection-std.com')
+TestHttp1_1('www.forwarded-connection-full.com')
+
+ts2 = Test.MakeATSProcess("ts2", command="traffic_manager", select_ports=False)
+
+ts2.Variables.port += 1
+
+baselineTsSetup(ts2, 4444)
+
+ts2.Disk.records_config.update({
+ 'proxy.config.url_remap.pristine_host_hdr': 1, # Retain Host header in original incoming client request.
+ 'proxy.config.http.insert_forwarded': 'by=uuid'})
+
+ts2.Disk.remap_config.AddLine(
+ 'map https://www.no-oride.com http://127.0.0.1:{0}'.format(server.Variables.Port)
+)
+
+# Forwarded header with UUID of 2nd ATS.
+tr = Test.AddTestRun()
+# Delay on readiness of our ssl ports
+tr.Processes.Default.StartBefore(Test.Processes.ts2, ready=CheckPort(ts2.Variables.ssl_port))
+#
+tr.Processes.Default.Command = (
+ 'curl --verbose --ipv4 --http1.1 --proxy localhost:{} http://www.no-oride.com'.format(ts2.Variables.port)
+)
+tr.Processes.Default.ReturnCode = 0
+
+# Call traffic_ctrl to set insert_forwarded
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+ 'traffic_ctl --debug config set proxy.config.http.insert_forwarded' +
+ ' "for|by=ip|by=unknown|by=servername|by=uuid|proto|host|connection=compact|connection=std|connection=full"'
+)
+tr.Processes.Default.ForceUseShell = False
+tr.Processes.Default.Env = ts2.Env
+tr.Processes.Default.ReturnCode = 0
+
+# HTTP 1.1
+tr = Test.AddTestRun()
+# Delay to give traffic_ctl config change time to take effect.
+tr.DelayStart = 15
+tr.Processes.Default.Command = (
+ 'curl --verbose --ipv4 --http1.1 --proxy localhost:{} http://www.no-oride.com'.format(ts2.Variables.port)
+)
+tr.Processes.Default.ReturnCode = 0
+
+# HTTP 1.0
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+ 'curl --verbose --ipv4 --http1.0 --proxy localhost:{} http://www.no-oride.com'.format(ts2.Variables.port)
+)
+tr.Processes.Default.ReturnCode = 0
+
+# HTTP 1.0 -- Forwarded headers already present
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+ "curl --verbose -H 'forwarded:for=0.6.6.6' -H 'forwarded:for=_argh' --ipv4 --http1.0" +
+ " --proxy localhost:{} http://www.no-oride.com".format(ts2.Variables.port)
+)
+tr.Processes.Default.ReturnCode = 0
+
+# HTTP 2
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+ 'curl --verbose --ipv4 --http2 --insecure --header "Host: www.no-oride.com"' +
+ ' https://localhost:{}'.format(ts2.Variables.ssl_port)
+)
+tr.Processes.Default.ReturnCode = 0
+
+# TLS
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+ 'curl --verbose --ipv4 --http1.1 --insecure --header "Host: www.no-oride.com" https://localhost:{}'
+ .format(ts2.Variables.ssl_port)
+)
+tr.Processes.Default.ReturnCode = 0
+
+# IPv6
+
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+ 'curl --verbose --ipv6 --http1.1 --proxy localhost:{} http://www.no-oride.com'.format(ts2.Variables.port)
+)
+tr.Processes.Default.ReturnCode = 0
+
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+ 'curl --verbose --ipv6 --http1.1 --insecure --header "Host: www.no-oride.com" https://localhost:{}'.format(ts2.Variables.ssl_port)
+)
+tr.Processes.Default.ReturnCode = 0
--
To stop receiving notification emails like this one, please contact
['"commits@trafficserver.apache.org" <co...@trafficserver.apache.org>'].