Skip to content

Commit 3398a16

Browse files
traeakzwoop
authored andcommitted
cache_range_requests plugin: detect and handle TSCacheUrlSet failures which poison the cache
(cherry picked from commit 8021a8e)
1 parent 0a2a286 commit 3398a16

3 files changed

Lines changed: 457 additions & 37 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
.. Licensed to the Apache Software Foundation (ASF) under one
2+
or more contributor license agreements. See the NOTICE file
3+
distributed with this work for additional information
4+
regarding copyright ownership. The ASF licenses this file
5+
to you under the Apache License, Version 2.0 (the
6+
"License"); you may not use this file except in compliance
7+
with the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing,
12+
software distributed under the License is distributed on an
13+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
KIND, either express or implied. See the License for the
15+
specific language governing permissions and limitations
16+
under the License.
17+
18+
19+
.. include:: ../../common.defs
20+
21+
.. _admin-plugins-cache-range-requests:
22+
23+
24+
Cache Range Requests Plugin
25+
***************************
26+
27+
Description
28+
===========
29+
30+
Most origin servers support HTTP/1.1 range requests (rfc 7233).
31+
ATS internally handles range request caching in one of 2 ways:
32+
33+
* Don't cache range requests.
34+
* Only server range requests from a wholly cached object.
35+
36+
This plugin allows you to remap individual range requests so that they
37+
are stored as individual objects in the ATS cache when subsequent range
38+
requests are likely to use the same range. This spreads range requests
39+
over multiple stripes thereby reducing I/O wait and system load averages.
40+
41+
:program:`cache_range_requests` reads the range request header byte range
42+
value and then creates a new ``cache key URL`` using the original request
43+
url with the range value appended to it. The range header is removed
44+
where appropriate from the requests and the origin server response code
45+
is changed from a 206 to a 200 to insure that the object is written to
46+
cache using the new cache key url. The response code sent to the client
47+
will be changed back to a 206 and all requests to the origin server will
48+
contain the range header so that the correct response is received.
49+
50+
The :program:`cache_range_requests` plugin by itself has no logic to
51+
efficiently manage overlapping ranges. It is best to use this plugin
52+
in conjunction with a smart client that only requests predetermined
53+
non overlapping cache ranges (request blocking) or as a helper for the
54+
:program:`slice` plugin.
55+
56+
Only requests which contain the ``Range: <units>=`` GET header
57+
will be served by the :program:`cache_range_requests` plugin.
58+
59+
If/when ATS implements partial object caching this plugin will
60+
become deprecated.
61+
62+
*NOTE* Given a multi range request the :program:`cache_range_requests`
63+
only processes the first range and ignores the rest.
64+
65+
How to run the plugin
66+
=====================
67+
68+
The plugin can run as a global plugin (a single global instance configured
69+
using :file:`plugin.config`) or as per-remap plugin (a separate instance
70+
configured per remap rule in :file:`remap.config`).
71+
72+
Global instance
73+
---------------
74+
75+
.. code::
76+
77+
$ cat plugin.config
78+
cache_range_request.so
79+
80+
81+
Per-remap instance
82+
------------------
83+
84+
.. code::
85+
86+
$cat remap.config
87+
map http://www.example.com http://www.origin.com \
88+
@plugin=cache_range_requests.so
89+
90+
91+
If both global and per-remap instance are used the per-remap configuration
92+
would take precedence (per-remap configuration would be applied and the
93+
global configuration ignored).
94+
95+
Plugin options
96+
==============
97+
98+
99+
Parent Selection as Cache Key
100+
-----------------------------
101+
102+
.. option:: --ps-cachekey
103+
.. option:: -p
104+
105+
Without this option parent selection is based solely on the hash of a
106+
URL Path a URL is requested from the same upstream parent cache listed
107+
in parent.config
108+
109+
110+
With this option parent selection is based on the full ``cache key URL``
111+
which includes information about the partial content range. In this mode,
112+
all requests (include partial content) will use consistent hashing method
113+
for parent selection.
114+
115+
116+
X-CRR-IMS header support
117+
------------------------
118+
119+
.. option:: --consider-ims
120+
.. option:: -c
121+
122+
To support slice plugin self healing an option to force revalidation
123+
after cache lookup complete was added. This option is triggered by a
124+
special header:
125+
126+
.. code::
127+
128+
X-CRR-IMS: Tue, 19 Nov 2019 13:26:45 GMT
129+
130+
When this header is provided and a `cache hit fresh` is encoutered the
131+
``Date`` header of the object in cache is compared to this header date
132+
value. If the cache date is *less* than this IMS date then the object
133+
is marked as STALE and an appropriate If-Modified-Since or If-Match
134+
request along with this X-CRR-IMS header is passed up to the parent.
135+
136+
In order for this to properly work in a CDN each cache in the
137+
chain *SHOULD* also contain a remap rule with the
138+
:program:`cache_range_requests` plugin with this option set.
139+
140+
Don't modify the Cache Key
141+
--------------------------
142+
143+
.. option:: --no-modify-cachekey
144+
.. option:: -n
145+
146+
With each transaction TSCacheUrlSet may only be called once. When
147+
using the `cache_range_requests` plugin in conjunction with the
148+
`cachekey` plugin the option `--include-headers=Range` should be
149+
added as a `cachekey` parameter with this option. Configuring this
150+
incorrectly *WILL* result in cache poisoning.
151+
152+
.. code::
153+
154+
map http://ats/ http://parent/ \
155+
@plugin=cachekey.so @pparam=--include-headers=Range \
156+
@plugin=cache_range_requests.so @pparam=--no-modify-cachekey
157+
158+
*Without this `cache_range_requests` plugin option*
159+
160+
*IF* the TSCacheUrlSet call in cache_range_requests fails, an error is
161+
generated in the logs and the cache_range_requests plugin will disable
162+
transaction caching in order to avoid cache poisoning.
163+
164+
Configuration examples
165+
======================
166+
167+
Global plugin
168+
-------------
169+
170+
.. code::
171+
172+
cache_range_requests.so --ps-cachekey --consider-ims --no-modify-cachekey
173+
174+
or
175+
176+
.. code::
177+
178+
cache_range_requests.so -p -c -n
179+
180+
Remap plugin
181+
------------
182+
183+
.. code::
184+
185+
map http://ats http://parent @plugin=cache_range_requests.so @pparam=--ps-cachekey @pparam=--consider-ims @pparam=--no-modify-cachekey
186+
187+
or
188+
189+
.. code::
190+
191+
map http://ats http://parent @plugin=cache_range_requests.so @pparam=-p @pparam=-c @pparam=-n

plugins/experimental/cache_range_requests/cache_range_requests.cc

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
* requests.
2727
*/
2828

29-
#include <cstdio>
30-
#include <cstring>
3129
#include "ts/ts.h"
3230
#include "ts/remap.h"
3331

32+
#include <cstdio>
33+
#include <cstring>
34+
#include <getopt.h>
35+
3436
#define PLUGIN_NAME "cache_range_requests"
3537
#define DEBUG_LOG(fmt, ...) TSDebug(PLUGIN_NAME, "[%s:%d] %s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
3638
#define ERROR_LOG(fmt, ...) TSError("[%s:%d] %s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
@@ -41,7 +43,8 @@ typedef enum parent_select_mode {
4143
} parent_select_mode_t;
4244

4345
struct pluginconfig {
44-
parent_select_mode_t ps_mode;
46+
parent_select_mode_t ps_mode{PS_DEFAULT};
47+
bool modify_cache_key{true};
4548
};
4649

4750
struct txndata {
@@ -56,7 +59,7 @@ static void handle_server_read_response(TSHttpTxn, struct txndata *);
5659
static int remove_header(TSMBuffer, TSMLoc, const char *, int);
5760
static bool set_header(TSMBuffer, TSMLoc, const char *, int, const char *, int);
5861
static int transaction_handler(TSCont, TSEvent, void *);
59-
static struct pluginconfig *create_pluginconfig(int argc, const char *argv[]);
62+
static struct pluginconfig *create_pluginconfig(int argc, char *const argv[]);
6063
static void delete_pluginconfig(struct pluginconfig *);
6164

6265
// pluginconfig struct (global plugin only)
@@ -68,26 +71,50 @@ static struct pluginconfig *gPluginConfig = nullptr;
6871
* Walk plugin argument list and updates config
6972
*/
7073
static struct pluginconfig *
71-
create_pluginconfig(int argc, const char *argv[])
74+
create_pluginconfig(int argc, char *const argv[])
7275
{
73-
struct pluginconfig *pc = nullptr;
74-
75-
pc = (struct pluginconfig *)TSmalloc(sizeof(struct pluginconfig));
76+
struct pluginconfig *pc = new pluginconfig;
7677

7778
if (nullptr == pc) {
7879
ERROR_LOG("Can't allocate pluginconfig");
7980
return nullptr;
8081
}
8182

82-
// Plugin uses default ATS selection (hash of URL path)
83-
pc->ps_mode = PS_DEFAULT;
83+
static const struct option longopts[] = {
84+
{const_cast<char *>("ps-cachekey"), no_argument, nullptr, 'p'},
85+
{const_cast<char *>("no-modify-cachekey"), no_argument, nullptr, 'n'},
86+
{nullptr, 0, nullptr, 0},
87+
};
8488

85-
// Walk through param list.
86-
for (int c = 0; c < argc; c++) {
87-
if (strcmp("ps_mode:cache_key_url", argv[c]) == 0) {
88-
pc->ps_mode = PS_CACHEKEY_URL;
89+
// getopt assumes args start at '1'
90+
++argc;
91+
--argv;
92+
93+
for (;;) {
94+
int const opt = getopt_long(argc, argv, "", longopts, nullptr);
95+
if (-1 == opt) {
8996
break;
9097
}
98+
99+
switch (opt) {
100+
case 'p': {
101+
DEBUG_LOG("Plugin modifies parent selection key");
102+
pc->ps_mode = PS_CACHEKEY_URL;
103+
} break;
104+
case 'n': {
105+
DEBUG_LOG("Plugin doesn't modify cache key");
106+
pc->modify_cache_key = false;
107+
} break;
108+
default: {
109+
DEBUG_LOG("Unknown option: '%c'", opt);
110+
} break;
111+
}
112+
}
113+
114+
// Backwards compatibility
115+
if (optind < argc && 0 == strcmp("ps_mode:cache_key_url", argv[optind])) {
116+
DEBUG_LOG("Plugin modifies parent selection key (deprecated)");
117+
pc->ps_mode = PS_CACHEKEY_URL;
91118
}
92119

93120
return pc;
@@ -101,7 +128,7 @@ delete_pluginconfig(struct pluginconfig *pc)
101128
{
102129
if (nullptr != pc) {
103130
DEBUG_LOG("Delete struct pluginconfig");
104-
TSfree(pc);
131+
delete pc;
105132
pc = nullptr;
106133
}
107134
}
@@ -165,23 +192,29 @@ range_header_check(TSHttpTxn txnp, struct pluginconfig *pc)
165192
TSfree(req_url);
166193
}
167194

168-
// set the cache key.
169-
if (TS_SUCCESS != TSCacheUrlSet(txnp, cache_key_url, cache_key_url_length)) {
170-
DEBUG_LOG("failed to change the cache url to %s.", cache_key_url);
171-
}
195+
if (nullptr != pc) {
196+
// set the cache key if configured to.
197+
if (pc->modify_cache_key && TS_SUCCESS != TSCacheUrlSet(txnp, cache_key_url, cache_key_url_length)) {
198+
ERROR_LOG("failed to change the cache url to %s.", cache_key_url);
199+
ERROR_LOG("Disabling cache for this transaction to avoid cache poisoning.");
200+
TSHttpTxnServerRespNoStoreSet(txnp, 1);
201+
TSHttpTxnRespCacheableSet(txnp, 0);
202+
TSHttpTxnReqCacheableSet(txnp, 0);
203+
}
172204

173-
// Optionally set the parent_selection_url to the cache_key url or path
174-
if (nullptr != pc && PS_DEFAULT != pc->ps_mode) {
175-
TSMLoc ps_loc = nullptr;
176-
177-
if (PS_CACHEKEY_URL == pc->ps_mode) {
178-
const char *start = cache_key_url;
179-
const char *end = cache_key_url + cache_key_url_length;
180-
if (TS_SUCCESS == TSUrlCreate(hdr_bufp, &ps_loc) &&
181-
TS_PARSE_DONE == TSUrlParse(hdr_bufp, ps_loc, &start, end) && // This should always succeed.
182-
TS_SUCCESS == TSHttpTxnParentSelectionUrlSet(txnp, hdr_bufp, ps_loc)) {
183-
DEBUG_LOG("Set Parent Selection URL to cache_key_url: %s", cache_key_url);
184-
TSHandleMLocRelease(hdr_bufp, TS_NULL_MLOC, ps_loc);
205+
// Optionally set the parent_selection_url to the cache_key url or path
206+
if (PS_DEFAULT != pc->ps_mode) {
207+
TSMLoc ps_loc = nullptr;
208+
209+
if (PS_CACHEKEY_URL == pc->ps_mode) {
210+
const char *start = cache_key_url;
211+
const char *end = cache_key_url + cache_key_url_length;
212+
if (TS_SUCCESS == TSUrlCreate(hdr_bufp, &ps_loc) &&
213+
TS_PARSE_DONE == TSUrlParse(hdr_bufp, ps_loc, &start, end) && // This should always succeed.
214+
TS_SUCCESS == TSHttpTxnParentSelectionUrlSet(txnp, hdr_bufp, ps_loc)) {
215+
DEBUG_LOG("Set Parent Selection URL to cache_key_url: %s", cache_key_url);
216+
TSHandleMLocRelease(hdr_bufp, TS_NULL_MLOC, ps_loc);
217+
}
185218
}
186219
}
187220
}
@@ -411,11 +444,10 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char * /*errbuf */, int /*
411444
}
412445

413446
// Skip over the Remap params
414-
const char **plugin_argv = const_cast<const char **>(argv + 2);
415-
argc -= 2;
447+
char *const *plugin_argv = const_cast<char *const *>(argv);
416448

417449
// Parse the argument list.
418-
*ih = (struct pluginconfig *)create_pluginconfig(argc, plugin_argv);
450+
*ih = (struct pluginconfig *)create_pluginconfig(argc - 2, plugin_argv + 2);
419451

420452
if (*ih == nullptr) {
421453
ERROR_LOG("Can't create pluginconfig");
@@ -472,9 +504,8 @@ TSPluginInit(int argc, const char *argv[])
472504
if (nullptr == gPluginConfig) {
473505
if (argc > 1) {
474506
// Skip ahead of first param (name of traffic server plugin shared object)
475-
const char **plugin_argv = const_cast<const char **>(argv + 1);
476-
argc -= 1;
477-
gPluginConfig = create_pluginconfig(argc, plugin_argv);
507+
char *const *plugin_argv = const_cast<char *const *>(argv);
508+
gPluginConfig = create_pluginconfig(argc - 1, plugin_argv + 1);
478509
}
479510
}
480511

0 commit comments

Comments
 (0)